From 19d6fc4883b3c112aafab33b9606da95d13e6647 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Tue, 21 Feb 2023 22:46:41 -0500 Subject: [PATCH 001/150] chore: add anoncreds-rs deps Signed-off-by: Daniel Bluhm --- requirements.anoncreds.txt | 1 + setup.py | 1 + 2 files changed, 2 insertions(+) create mode 100644 requirements.anoncreds.txt diff --git a/requirements.anoncreds.txt b/requirements.anoncreds.txt new file mode 100644 index 0000000000..f4b03a781d --- /dev/null +++ b/requirements.anoncreds.txt @@ -0,0 +1 @@ +anoncreds@git+https://github.com/hyperledger/anoncreds-rs#subdirectory=wrappers/python diff --git a/setup.py b/setup.py index d4a4d26ff0..10ede89740 100644 --- a/setup.py +++ b/setup.py @@ -39,6 +39,7 @@ def parse_requirements(filename): "askar": parse_requirements("requirements.askar.txt"), "indy": parse_requirements("requirements.indy.txt"), "bbs": parse_requirements("requirements.bbs.txt"), + "anoncreds": parse_requirements("requirements.anoncreds.txt"), "uvloop": {"uvloop": "^=0.14.0"}, }, python_requires=">=3.6.3", From 7b0cb6c487301d8cba67bb15a648d6dd14659444 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Tue, 21 Feb 2023 23:25:35 -0500 Subject: [PATCH 002/150] refactor: rename indy package to anoncreds Signed-off-by: Daniel Bluhm --- aries_cloudagent/{indy => anoncreds}/__init__.py | 0 .../{indy => anoncreds}/credx/__init__.py | 0 aries_cloudagent/{indy => anoncreds}/credx/holder.py | 0 aries_cloudagent/{indy => anoncreds}/credx/issuer.py | 0 .../{indy => anoncreds}/credx/tests/__init__.py | 0 .../credx/tests/test_cred_issuance.py | 0 .../{indy => anoncreds}/credx/verifier.py | 0 aries_cloudagent/{indy => anoncreds}/holder.py | 0 aries_cloudagent/{indy => anoncreds}/issuer.py | 0 .../{indy => anoncreds}/models/__init__.py | 0 aries_cloudagent/{indy => anoncreds}/models/cred.py | 0 .../{indy => anoncreds}/models/cred_abstract.py | 0 .../{indy => anoncreds}/models/cred_def.py | 0 .../{indy => anoncreds}/models/cred_precis.py | 0 .../{indy => anoncreds}/models/cred_request.py | 0 .../{indy => anoncreds}/models/non_rev_interval.py | 0 .../{indy => anoncreds}/models/predicate.py | 0 .../{indy => anoncreds}/models/pres_preview.py | 0 aries_cloudagent/{indy => anoncreds}/models/proof.py | 0 .../{indy => anoncreds}/models/proof_request.py | 0 .../{indy => anoncreds}/models/requested_creds.py | 0 .../{indy => anoncreds}/models/revocation.py | 0 .../{indy => anoncreds}/models/schema.py | 0 .../{indy => anoncreds}/models/tests/__init__.py | 0 .../{indy => anoncreds}/models/tests/test_cred.py | 0 .../models/tests/test_cred_precis.py | 0 .../models/tests/test_non_rev_interval.py | 0 .../{indy => anoncreds}/models/tests/test_pred.py | 0 .../models/tests/test_pres_preview.py | 0 .../{indy => anoncreds}/models/tests/test_proof.py | 0 .../models/tests/test_proof_request.py | 0 aries_cloudagent/{indy => anoncreds}/models/xform.py | 0 aries_cloudagent/{indy => anoncreds}/sdk/__init__.py | 0 aries_cloudagent/{indy => anoncreds}/sdk/error.py | 0 aries_cloudagent/{indy => anoncreds}/sdk/holder.py | 12 +++++------- aries_cloudagent/{indy => anoncreds}/sdk/issuer.py | 0 aries_cloudagent/{indy => anoncreds}/sdk/profile.py | 0 .../{indy => anoncreds}/sdk/tests/__init__.py | 0 .../{indy => anoncreds}/sdk/tests/test_holder.py | 0 .../{indy => anoncreds}/sdk/tests/test_issuer.py | 0 .../{indy => anoncreds}/sdk/tests/test_profile.py | 0 .../{indy => anoncreds}/sdk/tests/test_util.py | 0 .../{indy => anoncreds}/sdk/tests/test_verifier.py | 0 .../sdk/tests/test_wallet_plugin.py | 0 aries_cloudagent/{indy => anoncreds}/sdk/util.py | 0 aries_cloudagent/{indy => anoncreds}/sdk/verifier.py | 0 .../{indy => anoncreds}/sdk/wallet_plugin.py | 0 .../{indy => anoncreds}/sdk/wallet_setup.py | 0 .../{indy => anoncreds}/tests/__init__.py | 0 .../{indy => anoncreds}/tests/test_verifier.py | 0 aries_cloudagent/{indy => anoncreds}/util.py | 0 aries_cloudagent/{indy => anoncreds}/verifier.py | 0 aries_cloudagent/askar/profile.py | 6 +++--- aries_cloudagent/core/conductor.py | 2 +- aries_cloudagent/holder/routes.py | 4 ++-- aries_cloudagent/ledger/base.py | 2 +- aries_cloudagent/ledger/indy.py | 4 ++-- aries_cloudagent/revocation/manager.py | 2 +- aries_cloudagent/revocation/routes.py | 2 +- aries_cloudagent/storage/indy.py | 2 +- aries_cloudagent/wallet/indy.py | 4 ++-- 61 files changed, 19 insertions(+), 21 deletions(-) rename aries_cloudagent/{indy => anoncreds}/__init__.py (100%) rename aries_cloudagent/{indy => anoncreds}/credx/__init__.py (100%) rename aries_cloudagent/{indy => anoncreds}/credx/holder.py (100%) rename aries_cloudagent/{indy => anoncreds}/credx/issuer.py (100%) rename aries_cloudagent/{indy => anoncreds}/credx/tests/__init__.py (100%) rename aries_cloudagent/{indy => anoncreds}/credx/tests/test_cred_issuance.py (100%) rename aries_cloudagent/{indy => anoncreds}/credx/verifier.py (100%) rename aries_cloudagent/{indy => anoncreds}/holder.py (100%) rename aries_cloudagent/{indy => anoncreds}/issuer.py (100%) rename aries_cloudagent/{indy => anoncreds}/models/__init__.py (100%) rename aries_cloudagent/{indy => anoncreds}/models/cred.py (100%) rename aries_cloudagent/{indy => anoncreds}/models/cred_abstract.py (100%) rename aries_cloudagent/{indy => anoncreds}/models/cred_def.py (100%) rename aries_cloudagent/{indy => anoncreds}/models/cred_precis.py (100%) rename aries_cloudagent/{indy => anoncreds}/models/cred_request.py (100%) rename aries_cloudagent/{indy => anoncreds}/models/non_rev_interval.py (100%) rename aries_cloudagent/{indy => anoncreds}/models/predicate.py (100%) rename aries_cloudagent/{indy => anoncreds}/models/pres_preview.py (100%) rename aries_cloudagent/{indy => anoncreds}/models/proof.py (100%) rename aries_cloudagent/{indy => anoncreds}/models/proof_request.py (100%) rename aries_cloudagent/{indy => anoncreds}/models/requested_creds.py (100%) rename aries_cloudagent/{indy => anoncreds}/models/revocation.py (100%) rename aries_cloudagent/{indy => anoncreds}/models/schema.py (100%) rename aries_cloudagent/{indy => anoncreds}/models/tests/__init__.py (100%) rename aries_cloudagent/{indy => anoncreds}/models/tests/test_cred.py (100%) rename aries_cloudagent/{indy => anoncreds}/models/tests/test_cred_precis.py (100%) rename aries_cloudagent/{indy => anoncreds}/models/tests/test_non_rev_interval.py (100%) rename aries_cloudagent/{indy => anoncreds}/models/tests/test_pred.py (100%) rename aries_cloudagent/{indy => anoncreds}/models/tests/test_pres_preview.py (100%) rename aries_cloudagent/{indy => anoncreds}/models/tests/test_proof.py (100%) rename aries_cloudagent/{indy => anoncreds}/models/tests/test_proof_request.py (100%) rename aries_cloudagent/{indy => anoncreds}/models/xform.py (100%) rename aries_cloudagent/{indy => anoncreds}/sdk/__init__.py (100%) rename aries_cloudagent/{indy => anoncreds}/sdk/error.py (100%) rename aries_cloudagent/{indy => anoncreds}/sdk/holder.py (98%) rename aries_cloudagent/{indy => anoncreds}/sdk/issuer.py (100%) rename aries_cloudagent/{indy => anoncreds}/sdk/profile.py (100%) rename aries_cloudagent/{indy => anoncreds}/sdk/tests/__init__.py (100%) rename aries_cloudagent/{indy => anoncreds}/sdk/tests/test_holder.py (100%) rename aries_cloudagent/{indy => anoncreds}/sdk/tests/test_issuer.py (100%) rename aries_cloudagent/{indy => anoncreds}/sdk/tests/test_profile.py (100%) rename aries_cloudagent/{indy => anoncreds}/sdk/tests/test_util.py (100%) rename aries_cloudagent/{indy => anoncreds}/sdk/tests/test_verifier.py (100%) rename aries_cloudagent/{indy => anoncreds}/sdk/tests/test_wallet_plugin.py (100%) rename aries_cloudagent/{indy => anoncreds}/sdk/util.py (100%) rename aries_cloudagent/{indy => anoncreds}/sdk/verifier.py (100%) rename aries_cloudagent/{indy => anoncreds}/sdk/wallet_plugin.py (100%) rename aries_cloudagent/{indy => anoncreds}/sdk/wallet_setup.py (100%) rename aries_cloudagent/{indy => anoncreds}/tests/__init__.py (100%) rename aries_cloudagent/{indy => anoncreds}/tests/test_verifier.py (100%) rename aries_cloudagent/{indy => anoncreds}/util.py (100%) rename aries_cloudagent/{indy => anoncreds}/verifier.py (100%) diff --git a/aries_cloudagent/indy/__init__.py b/aries_cloudagent/anoncreds/__init__.py similarity index 100% rename from aries_cloudagent/indy/__init__.py rename to aries_cloudagent/anoncreds/__init__.py diff --git a/aries_cloudagent/indy/credx/__init__.py b/aries_cloudagent/anoncreds/credx/__init__.py similarity index 100% rename from aries_cloudagent/indy/credx/__init__.py rename to aries_cloudagent/anoncreds/credx/__init__.py diff --git a/aries_cloudagent/indy/credx/holder.py b/aries_cloudagent/anoncreds/credx/holder.py similarity index 100% rename from aries_cloudagent/indy/credx/holder.py rename to aries_cloudagent/anoncreds/credx/holder.py diff --git a/aries_cloudagent/indy/credx/issuer.py b/aries_cloudagent/anoncreds/credx/issuer.py similarity index 100% rename from aries_cloudagent/indy/credx/issuer.py rename to aries_cloudagent/anoncreds/credx/issuer.py diff --git a/aries_cloudagent/indy/credx/tests/__init__.py b/aries_cloudagent/anoncreds/credx/tests/__init__.py similarity index 100% rename from aries_cloudagent/indy/credx/tests/__init__.py rename to aries_cloudagent/anoncreds/credx/tests/__init__.py diff --git a/aries_cloudagent/indy/credx/tests/test_cred_issuance.py b/aries_cloudagent/anoncreds/credx/tests/test_cred_issuance.py similarity index 100% rename from aries_cloudagent/indy/credx/tests/test_cred_issuance.py rename to aries_cloudagent/anoncreds/credx/tests/test_cred_issuance.py diff --git a/aries_cloudagent/indy/credx/verifier.py b/aries_cloudagent/anoncreds/credx/verifier.py similarity index 100% rename from aries_cloudagent/indy/credx/verifier.py rename to aries_cloudagent/anoncreds/credx/verifier.py diff --git a/aries_cloudagent/indy/holder.py b/aries_cloudagent/anoncreds/holder.py similarity index 100% rename from aries_cloudagent/indy/holder.py rename to aries_cloudagent/anoncreds/holder.py diff --git a/aries_cloudagent/indy/issuer.py b/aries_cloudagent/anoncreds/issuer.py similarity index 100% rename from aries_cloudagent/indy/issuer.py rename to aries_cloudagent/anoncreds/issuer.py diff --git a/aries_cloudagent/indy/models/__init__.py b/aries_cloudagent/anoncreds/models/__init__.py similarity index 100% rename from aries_cloudagent/indy/models/__init__.py rename to aries_cloudagent/anoncreds/models/__init__.py diff --git a/aries_cloudagent/indy/models/cred.py b/aries_cloudagent/anoncreds/models/cred.py similarity index 100% rename from aries_cloudagent/indy/models/cred.py rename to aries_cloudagent/anoncreds/models/cred.py diff --git a/aries_cloudagent/indy/models/cred_abstract.py b/aries_cloudagent/anoncreds/models/cred_abstract.py similarity index 100% rename from aries_cloudagent/indy/models/cred_abstract.py rename to aries_cloudagent/anoncreds/models/cred_abstract.py diff --git a/aries_cloudagent/indy/models/cred_def.py b/aries_cloudagent/anoncreds/models/cred_def.py similarity index 100% rename from aries_cloudagent/indy/models/cred_def.py rename to aries_cloudagent/anoncreds/models/cred_def.py diff --git a/aries_cloudagent/indy/models/cred_precis.py b/aries_cloudagent/anoncreds/models/cred_precis.py similarity index 100% rename from aries_cloudagent/indy/models/cred_precis.py rename to aries_cloudagent/anoncreds/models/cred_precis.py diff --git a/aries_cloudagent/indy/models/cred_request.py b/aries_cloudagent/anoncreds/models/cred_request.py similarity index 100% rename from aries_cloudagent/indy/models/cred_request.py rename to aries_cloudagent/anoncreds/models/cred_request.py diff --git a/aries_cloudagent/indy/models/non_rev_interval.py b/aries_cloudagent/anoncreds/models/non_rev_interval.py similarity index 100% rename from aries_cloudagent/indy/models/non_rev_interval.py rename to aries_cloudagent/anoncreds/models/non_rev_interval.py diff --git a/aries_cloudagent/indy/models/predicate.py b/aries_cloudagent/anoncreds/models/predicate.py similarity index 100% rename from aries_cloudagent/indy/models/predicate.py rename to aries_cloudagent/anoncreds/models/predicate.py diff --git a/aries_cloudagent/indy/models/pres_preview.py b/aries_cloudagent/anoncreds/models/pres_preview.py similarity index 100% rename from aries_cloudagent/indy/models/pres_preview.py rename to aries_cloudagent/anoncreds/models/pres_preview.py diff --git a/aries_cloudagent/indy/models/proof.py b/aries_cloudagent/anoncreds/models/proof.py similarity index 100% rename from aries_cloudagent/indy/models/proof.py rename to aries_cloudagent/anoncreds/models/proof.py diff --git a/aries_cloudagent/indy/models/proof_request.py b/aries_cloudagent/anoncreds/models/proof_request.py similarity index 100% rename from aries_cloudagent/indy/models/proof_request.py rename to aries_cloudagent/anoncreds/models/proof_request.py diff --git a/aries_cloudagent/indy/models/requested_creds.py b/aries_cloudagent/anoncreds/models/requested_creds.py similarity index 100% rename from aries_cloudagent/indy/models/requested_creds.py rename to aries_cloudagent/anoncreds/models/requested_creds.py diff --git a/aries_cloudagent/indy/models/revocation.py b/aries_cloudagent/anoncreds/models/revocation.py similarity index 100% rename from aries_cloudagent/indy/models/revocation.py rename to aries_cloudagent/anoncreds/models/revocation.py diff --git a/aries_cloudagent/indy/models/schema.py b/aries_cloudagent/anoncreds/models/schema.py similarity index 100% rename from aries_cloudagent/indy/models/schema.py rename to aries_cloudagent/anoncreds/models/schema.py diff --git a/aries_cloudagent/indy/models/tests/__init__.py b/aries_cloudagent/anoncreds/models/tests/__init__.py similarity index 100% rename from aries_cloudagent/indy/models/tests/__init__.py rename to aries_cloudagent/anoncreds/models/tests/__init__.py diff --git a/aries_cloudagent/indy/models/tests/test_cred.py b/aries_cloudagent/anoncreds/models/tests/test_cred.py similarity index 100% rename from aries_cloudagent/indy/models/tests/test_cred.py rename to aries_cloudagent/anoncreds/models/tests/test_cred.py diff --git a/aries_cloudagent/indy/models/tests/test_cred_precis.py b/aries_cloudagent/anoncreds/models/tests/test_cred_precis.py similarity index 100% rename from aries_cloudagent/indy/models/tests/test_cred_precis.py rename to aries_cloudagent/anoncreds/models/tests/test_cred_precis.py diff --git a/aries_cloudagent/indy/models/tests/test_non_rev_interval.py b/aries_cloudagent/anoncreds/models/tests/test_non_rev_interval.py similarity index 100% rename from aries_cloudagent/indy/models/tests/test_non_rev_interval.py rename to aries_cloudagent/anoncreds/models/tests/test_non_rev_interval.py diff --git a/aries_cloudagent/indy/models/tests/test_pred.py b/aries_cloudagent/anoncreds/models/tests/test_pred.py similarity index 100% rename from aries_cloudagent/indy/models/tests/test_pred.py rename to aries_cloudagent/anoncreds/models/tests/test_pred.py diff --git a/aries_cloudagent/indy/models/tests/test_pres_preview.py b/aries_cloudagent/anoncreds/models/tests/test_pres_preview.py similarity index 100% rename from aries_cloudagent/indy/models/tests/test_pres_preview.py rename to aries_cloudagent/anoncreds/models/tests/test_pres_preview.py diff --git a/aries_cloudagent/indy/models/tests/test_proof.py b/aries_cloudagent/anoncreds/models/tests/test_proof.py similarity index 100% rename from aries_cloudagent/indy/models/tests/test_proof.py rename to aries_cloudagent/anoncreds/models/tests/test_proof.py diff --git a/aries_cloudagent/indy/models/tests/test_proof_request.py b/aries_cloudagent/anoncreds/models/tests/test_proof_request.py similarity index 100% rename from aries_cloudagent/indy/models/tests/test_proof_request.py rename to aries_cloudagent/anoncreds/models/tests/test_proof_request.py diff --git a/aries_cloudagent/indy/models/xform.py b/aries_cloudagent/anoncreds/models/xform.py similarity index 100% rename from aries_cloudagent/indy/models/xform.py rename to aries_cloudagent/anoncreds/models/xform.py diff --git a/aries_cloudagent/indy/sdk/__init__.py b/aries_cloudagent/anoncreds/sdk/__init__.py similarity index 100% rename from aries_cloudagent/indy/sdk/__init__.py rename to aries_cloudagent/anoncreds/sdk/__init__.py diff --git a/aries_cloudagent/indy/sdk/error.py b/aries_cloudagent/anoncreds/sdk/error.py similarity index 100% rename from aries_cloudagent/indy/sdk/error.py rename to aries_cloudagent/anoncreds/sdk/error.py diff --git a/aries_cloudagent/indy/sdk/holder.py b/aries_cloudagent/anoncreds/sdk/holder.py similarity index 98% rename from aries_cloudagent/indy/sdk/holder.py rename to aries_cloudagent/anoncreds/sdk/holder.py index efb1a4f3ba..48cb2abac0 100644 --- a/aries_cloudagent/indy/sdk/holder.py +++ b/aries_cloudagent/anoncreds/sdk/holder.py @@ -226,13 +226,11 @@ async def fetch(reft, limit): with IndyErrorHandler( "Error when constructing wallet credential query", IndyHolderError ): - search_handle = ( - await ( - indy.anoncreds.prover_search_credentials_for_proof_req( - self.wallet.handle, - json.dumps(presentation_request), - json.dumps(extra_query), - ) + search_handle = await ( + indy.anoncreds.prover_search_credentials_for_proof_req( + self.wallet.handle, + json.dumps(presentation_request), + json.dumps(extra_query), ) ) diff --git a/aries_cloudagent/indy/sdk/issuer.py b/aries_cloudagent/anoncreds/sdk/issuer.py similarity index 100% rename from aries_cloudagent/indy/sdk/issuer.py rename to aries_cloudagent/anoncreds/sdk/issuer.py diff --git a/aries_cloudagent/indy/sdk/profile.py b/aries_cloudagent/anoncreds/sdk/profile.py similarity index 100% rename from aries_cloudagent/indy/sdk/profile.py rename to aries_cloudagent/anoncreds/sdk/profile.py diff --git a/aries_cloudagent/indy/sdk/tests/__init__.py b/aries_cloudagent/anoncreds/sdk/tests/__init__.py similarity index 100% rename from aries_cloudagent/indy/sdk/tests/__init__.py rename to aries_cloudagent/anoncreds/sdk/tests/__init__.py diff --git a/aries_cloudagent/indy/sdk/tests/test_holder.py b/aries_cloudagent/anoncreds/sdk/tests/test_holder.py similarity index 100% rename from aries_cloudagent/indy/sdk/tests/test_holder.py rename to aries_cloudagent/anoncreds/sdk/tests/test_holder.py diff --git a/aries_cloudagent/indy/sdk/tests/test_issuer.py b/aries_cloudagent/anoncreds/sdk/tests/test_issuer.py similarity index 100% rename from aries_cloudagent/indy/sdk/tests/test_issuer.py rename to aries_cloudagent/anoncreds/sdk/tests/test_issuer.py diff --git a/aries_cloudagent/indy/sdk/tests/test_profile.py b/aries_cloudagent/anoncreds/sdk/tests/test_profile.py similarity index 100% rename from aries_cloudagent/indy/sdk/tests/test_profile.py rename to aries_cloudagent/anoncreds/sdk/tests/test_profile.py diff --git a/aries_cloudagent/indy/sdk/tests/test_util.py b/aries_cloudagent/anoncreds/sdk/tests/test_util.py similarity index 100% rename from aries_cloudagent/indy/sdk/tests/test_util.py rename to aries_cloudagent/anoncreds/sdk/tests/test_util.py diff --git a/aries_cloudagent/indy/sdk/tests/test_verifier.py b/aries_cloudagent/anoncreds/sdk/tests/test_verifier.py similarity index 100% rename from aries_cloudagent/indy/sdk/tests/test_verifier.py rename to aries_cloudagent/anoncreds/sdk/tests/test_verifier.py diff --git a/aries_cloudagent/indy/sdk/tests/test_wallet_plugin.py b/aries_cloudagent/anoncreds/sdk/tests/test_wallet_plugin.py similarity index 100% rename from aries_cloudagent/indy/sdk/tests/test_wallet_plugin.py rename to aries_cloudagent/anoncreds/sdk/tests/test_wallet_plugin.py diff --git a/aries_cloudagent/indy/sdk/util.py b/aries_cloudagent/anoncreds/sdk/util.py similarity index 100% rename from aries_cloudagent/indy/sdk/util.py rename to aries_cloudagent/anoncreds/sdk/util.py diff --git a/aries_cloudagent/indy/sdk/verifier.py b/aries_cloudagent/anoncreds/sdk/verifier.py similarity index 100% rename from aries_cloudagent/indy/sdk/verifier.py rename to aries_cloudagent/anoncreds/sdk/verifier.py diff --git a/aries_cloudagent/indy/sdk/wallet_plugin.py b/aries_cloudagent/anoncreds/sdk/wallet_plugin.py similarity index 100% rename from aries_cloudagent/indy/sdk/wallet_plugin.py rename to aries_cloudagent/anoncreds/sdk/wallet_plugin.py diff --git a/aries_cloudagent/indy/sdk/wallet_setup.py b/aries_cloudagent/anoncreds/sdk/wallet_setup.py similarity index 100% rename from aries_cloudagent/indy/sdk/wallet_setup.py rename to aries_cloudagent/anoncreds/sdk/wallet_setup.py diff --git a/aries_cloudagent/indy/tests/__init__.py b/aries_cloudagent/anoncreds/tests/__init__.py similarity index 100% rename from aries_cloudagent/indy/tests/__init__.py rename to aries_cloudagent/anoncreds/tests/__init__.py diff --git a/aries_cloudagent/indy/tests/test_verifier.py b/aries_cloudagent/anoncreds/tests/test_verifier.py similarity index 100% rename from aries_cloudagent/indy/tests/test_verifier.py rename to aries_cloudagent/anoncreds/tests/test_verifier.py diff --git a/aries_cloudagent/indy/util.py b/aries_cloudagent/anoncreds/util.py similarity index 100% rename from aries_cloudagent/indy/util.py rename to aries_cloudagent/anoncreds/util.py diff --git a/aries_cloudagent/indy/verifier.py b/aries_cloudagent/anoncreds/verifier.py similarity index 100% rename from aries_cloudagent/indy/verifier.py rename to aries_cloudagent/anoncreds/verifier.py diff --git a/aries_cloudagent/askar/profile.py b/aries_cloudagent/askar/profile.py index a8cb10df71..3a31a73859 100644 --- a/aries_cloudagent/askar/profile.py +++ b/aries_cloudagent/askar/profile.py @@ -16,9 +16,9 @@ from ..config.provider import ClassProvider from ..core.error import ProfileError from ..core.profile import Profile, ProfileManager, ProfileSession -from ..indy.holder import IndyHolder -from ..indy.issuer import IndyIssuer -from ..indy.verifier import IndyVerifier +from ..anoncreds.holder import IndyHolder +from ..anoncreds.issuer import IndyIssuer +from ..anoncreds.verifier import IndyVerifier from ..ledger.base import BaseLedger from ..ledger.indy_vdr import IndyVdrLedger, IndyVdrLedgerPool from ..storage.base import BaseStorage, BaseStorageSearch diff --git a/aries_cloudagent/core/conductor.py b/aries_cloudagent/core/conductor.py index 1f09dbc13a..25714b7048 100644 --- a/aries_cloudagent/core/conductor.py +++ b/aries_cloudagent/core/conductor.py @@ -27,7 +27,7 @@ from ..config.provider import ClassProvider from ..config.wallet import wallet_config from ..core.profile import Profile -from ..indy.verifier import IndyVerifier +from ..anoncreds.verifier import IndyVerifier from ..ledger.error import LedgerConfigError, LedgerTransactionError from ..ledger.multiple_ledger.base_manager import ( diff --git a/aries_cloudagent/holder/routes.py b/aries_cloudagent/holder/routes.py index 26032000b5..cf824655f7 100644 --- a/aries_cloudagent/holder/routes.py +++ b/aries_cloudagent/holder/routes.py @@ -13,8 +13,8 @@ from marshmallow import fields from ..admin.request_context import AdminRequestContext -from ..indy.holder import IndyHolder, IndyHolderError -from ..indy.models.cred_precis import IndyCredInfoSchema +from ..anoncreds.holder import IndyHolder, IndyHolderError +from ..anoncreds.models.cred_precis import IndyCredInfoSchema from ..ledger.base import BaseLedger from ..ledger.error import LedgerError from ..messaging.models.openapi import OpenAPISchema diff --git a/aries_cloudagent/ledger/base.py b/aries_cloudagent/ledger/base.py index 06bd362d95..b3f0604104 100644 --- a/aries_cloudagent/ledger/base.py +++ b/aries_cloudagent/ledger/base.py @@ -9,7 +9,7 @@ from hashlib import sha256 from typing import List, Sequence, Tuple, Union -from ..indy.issuer import DEFAULT_CRED_DEF_TAG, IndyIssuer, IndyIssuerError +from ..anoncreds.issuer import DEFAULT_CRED_DEF_TAG, IndyIssuer, IndyIssuerError from ..utils import sentinel from ..wallet.did_info import DIDInfo diff --git a/aries_cloudagent/ledger/indy.py b/aries_cloudagent/ledger/indy.py index a127f8c028..48a471a7ce 100644 --- a/aries_cloudagent/ledger/indy.py +++ b/aries_cloudagent/ledger/indy.py @@ -16,7 +16,7 @@ from ..cache.base import BaseCache from ..config.base import BaseInjector, BaseProvider, BaseSettings -from ..indy.sdk.error import IndyErrorHandler +from ..anoncreds.sdk.error import IndyErrorHandler from ..storage.base import StorageRecord from ..storage.indy import IndySdkStorage from ..utils import sentinel @@ -37,7 +37,7 @@ from .util import TAA_ACCEPTED_RECORD_TYPE if TYPE_CHECKING: - from ..indy.sdk.profile import IndySdkProfile + from ..anoncreds.sdk.profile import IndySdkProfile LOGGER = logging.getLogger(__name__) diff --git a/aries_cloudagent/revocation/manager.py b/aries_cloudagent/revocation/manager.py index 592a879c7a..334f5a4d33 100644 --- a/aries_cloudagent/revocation/manager.py +++ b/aries_cloudagent/revocation/manager.py @@ -9,7 +9,7 @@ ) from ..core.error import BaseError from ..core.profile import Profile -from ..indy.issuer import IndyIssuer +from ..anoncreds.issuer import IndyIssuer from ..storage.error import StorageNotFoundError from .indy import IndyRevocation from .models.issuer_cred_rev_record import IssuerCredRevRecord diff --git a/aries_cloudagent/revocation/routes.py b/aries_cloudagent/revocation/routes.py index b637757b43..6f2b17db9e 100644 --- a/aries_cloudagent/revocation/routes.py +++ b/aries_cloudagent/revocation/routes.py @@ -22,7 +22,7 @@ from ..connections.models.conn_record import ConnRecord from ..core.event_bus import Event, EventBus from ..core.profile import Profile -from ..indy.issuer import IndyIssuerError +from ..anoncreds.issuer import IndyIssuerError from ..ledger.base import BaseLedger from ..ledger.multiple_ledger.base_manager import BaseMultipleLedgerManager from ..ledger.error import LedgerError diff --git a/aries_cloudagent/storage/indy.py b/aries_cloudagent/storage/indy.py index 9a5e37aec8..9413ab146b 100644 --- a/aries_cloudagent/storage/indy.py +++ b/aries_cloudagent/storage/indy.py @@ -22,7 +22,7 @@ StorageSearchError, ) from .record import StorageRecord -from ..indy.sdk.wallet_setup import IndyOpenWallet +from ..anoncreds.sdk.wallet_setup import IndyOpenWallet LOGGER = logging.getLogger(__name__) diff --git a/aries_cloudagent/wallet/indy.py b/aries_cloudagent/wallet/indy.py index 78356946bb..5d8d2f7e6b 100644 --- a/aries_cloudagent/wallet/indy.py +++ b/aries_cloudagent/wallet/indy.py @@ -13,8 +13,8 @@ from indy.error import IndyError, ErrorCode from ..did.did_key import DIDKey -from ..indy.sdk.error import IndyErrorHandler -from ..indy.sdk.wallet_setup import IndyOpenWallet +from ..anoncreds.sdk.error import IndyErrorHandler +from ..anoncreds.sdk.wallet_setup import IndyOpenWallet from ..ledger.base import BaseLedger from ..ledger.endpoint_type import EndpointType from ..ledger.error import LedgerConfigError From ee3d40a507b72178e7e2b2a8d5b8acaa7eb8ee59 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Tue, 21 Feb 2023 23:40:36 -0500 Subject: [PATCH 003/150] refactor: rename Indy* to AnonCreds* Signed-off-by: Daniel Bluhm --- aries_cloudagent/anoncreds/credx/holder.py | 62 +++++++------ aries_cloudagent/anoncreds/credx/issuer.py | 88 +++++++++++-------- aries_cloudagent/anoncreds/credx/verifier.py | 4 +- aries_cloudagent/anoncreds/holder.py | 4 +- aries_cloudagent/anoncreds/issuer.py | 6 +- aries_cloudagent/anoncreds/sdk/holder.py | 42 ++++----- aries_cloudagent/anoncreds/sdk/issuer.py | 24 ++--- aries_cloudagent/anoncreds/sdk/profile.py | 8 +- .../anoncreds/sdk/tests/test_holder.py | 28 +++--- .../anoncreds/sdk/tests/test_issuer.py | 12 +-- aries_cloudagent/anoncreds/sdk/verifier.py | 4 +- .../anoncreds/tests/test_verifier.py | 4 +- aries_cloudagent/anoncreds/verifier.py | 2 +- aries_cloudagent/askar/profile.py | 8 +- aries_cloudagent/core/conductor.py | 6 +- aries_cloudagent/holder/routes.py | 14 +-- aries_cloudagent/holder/tests/test_routes.py | 2 +- aries_cloudagent/ledger/base.py | 18 ++-- aries_cloudagent/revocation/manager.py | 6 +- aries_cloudagent/revocation/routes.py | 8 +- 20 files changed, 192 insertions(+), 158 deletions(-) diff --git a/aries_cloudagent/anoncreds/credx/holder.py b/aries_cloudagent/anoncreds/credx/holder.py index a212661cbc..ba53d23730 100644 --- a/aries_cloudagent/anoncreds/credx/holder.py +++ b/aries_cloudagent/anoncreds/credx/holder.py @@ -23,7 +23,7 @@ from ...ledger.base import BaseLedger from ...wallet.error import WalletNotFoundError -from ..holder import IndyHolder, IndyHolderError +from ..holder import AnonCredsHolder, AnonCredsHolderError LOGGER = logging.getLogger(__name__) @@ -48,7 +48,7 @@ def _normalize_attr_name(name: str) -> str: return name.replace(" ", "") -class IndyCredxHolder(IndyHolder): +class IndyCredxHolder(AnonCredsHolder): """Indy-credx holder class.""" MASTER_SECRET_ID = "default" @@ -78,18 +78,22 @@ async def get_master_secret(self) -> MasterSecret: CATEGORY_MASTER_SECRET, IndyCredxHolder.MASTER_SECRET_ID ) except AskarError as err: - raise IndyHolderError("Error fetching master secret") from err + raise AnonCredsHolderError("Error fetching master secret") from err if record: try: secret = MasterSecret.load(record.raw_value) except CredxError as err: - raise IndyHolderError("Error loading master secret") from err + raise AnonCredsHolderError( + "Error loading master secret" + ) from err break else: try: secret = MasterSecret.create() except CredxError as err: - raise IndyHolderError("Error creating master secret") from err + raise AnonCredsHolderError( + "Error creating master secret" + ) from err try: await session.handle.insert( CATEGORY_MASTER_SECRET, @@ -98,7 +102,9 @@ async def get_master_secret(self) -> MasterSecret: ) except AskarError as err: if err.code != AskarErrorCode.DUPLICATE: - raise IndyHolderError("Error saving master secret") from err + raise AnonCredsHolderError( + "Error saving master secret" + ) from err # else: lost race to create record, retry else: break @@ -134,7 +140,7 @@ async def create_credential_request( credential_offer, ) except CredxError as err: - raise IndyHolderError("Error creating credential request") from err + raise AnonCredsHolderError("Error creating credential request") from err cred_req_json, cred_req_metadata_json = ( cred_req.to_json(), cred_req_metadata.to_json(), @@ -187,16 +193,18 @@ async def store_credential( rev_reg_def, ) except CredxError as err: - raise IndyHolderError("Error processing received credential") from err + raise AnonCredsHolderError("Error processing received credential") from err schema_id = cred_recvd.schema_id schema_id_parts = re.match(r"^(\w+):2:([^:]+):([^:]+)$", schema_id) if not schema_id_parts: - raise IndyHolderError(f"Error parsing credential schema ID: {schema_id}") + raise AnonCredsHolderError( + f"Error parsing credential schema ID: {schema_id}" + ) cred_def_id = cred_recvd.cred_def_id cdef_id_parts = re.match(r"^(\w+):3:CL:([^:]+):([^:]+)$", cred_def_id) if not cdef_id_parts: - raise IndyHolderError( + raise AnonCredsHolderError( f"Error parsing credential definition ID: {cred_def_id}" ) @@ -231,13 +239,13 @@ async def store_credential( ) if mime_types: await txn.handle.insert( - IndyHolder.RECORD_TYPE_MIME_TYPES, + AnonCredsHolder.RECORD_TYPE_MIME_TYPES, credential_id, value_json=mime_types, ) await txn.commit() except AskarError as err: - raise IndyHolderError("Error storing credential") from err + raise AnonCredsHolderError("Error storing credential") from err return credential_id @@ -266,9 +274,9 @@ async def get_credentials(self, start: int, count: int, wql: dict): cred = Credential.load(row.raw_value) result.append(_make_cred_info(row.name, cred)) except AskarError as err: - raise IndyHolderError("Error retrieving credentials") from err + raise AnonCredsHolderError("Error retrieving credentials") from err except CredxError as err: - raise IndyHolderError("Error loading stored credential") from err + raise AnonCredsHolderError("Error loading stored credential") from err return result @@ -318,7 +326,9 @@ async def get_credentials_for_presentation_request_by_referent( # tag_filter[f"attr::{_normalize_attr_name(name)}::marker"] = "1" restr = pred.get("restrictions") else: - raise IndyHolderError(f"Unknown presentation request referent: {reft}") + raise AnonCredsHolderError( + f"Unknown presentation request referent: {reft}" + ) tag_filter = {"$exist": list(f"attr::{name}::value" for name in names)} if restr: @@ -369,7 +379,7 @@ async def _get_credential(self, credential_id: str) -> Credential: async with self._profile.session() as session: cred = await session.handle.fetch(CATEGORY_CREDENTIAL, credential_id) except AskarError as err: - raise IndyHolderError("Error retrieving credential") from err + raise AnonCredsHolderError("Error retrieving credential") from err if not cred: raise WalletNotFoundError( @@ -379,7 +389,7 @@ async def _get_credential(self, credential_id: str) -> Credential: try: return Credential.load(cred.raw_value) except CredxError as err: - raise IndyHolderError("Error loading requested credential") from err + raise AnonCredsHolderError("Error loading requested credential") from err async def credential_revoked( self, ledger: BaseLedger, credential_id: str, fro: int = None, to: int = None @@ -417,13 +427,13 @@ async def delete_credential(self, credential_id: str): async with self._profile.session() as session: await session.handle.remove(CATEGORY_CREDENTIAL, credential_id) await session.handle.remove( - IndyHolder.RECORD_TYPE_MIME_TYPES, credential_id + AnonCredsHolder.RECORD_TYPE_MIME_TYPES, credential_id ) except AskarError as err: if err.code == AskarErrorCode.NOT_FOUND: pass else: - raise IndyHolderError("Error deleting credential") from err + raise AnonCredsHolderError("Error deleting credential") from err async def get_mime_type( self, credential_id: str, attr: str = None @@ -442,11 +452,13 @@ async def get_mime_type( try: async with self._profile.session() as session: mime_types_record = await session.handle.fetch( - IndyHolder.RECORD_TYPE_MIME_TYPES, + AnonCredsHolder.RECORD_TYPE_MIME_TYPES, credential_id, ) except AskarError as err: - raise IndyHolderError("Error retrieving credential mime types") from err + raise AnonCredsHolderError( + "Error retrieving credential mime types" + ) from err values = mime_types_record and mime_types_record.value_json if values: return values.get(attr) if attr else values @@ -480,13 +492,13 @@ def get_rev_state(cred_id: str, detail: dict): rev_state = None if timestamp: if not rev_states or rev_reg_id not in rev_states: - raise IndyHolderError( + raise AnonCredsHolderError( f"No revocation states provided for credential '{cred_id}' " f"with rev_reg_id '{rev_reg_id}'" ) rev_state = rev_states[rev_reg_id].get(timestamp) if not rev_state: - raise IndyHolderError( + raise AnonCredsHolderError( f"No revocation states provided for credential '{cred_id}' " f"with rev_reg_id '{rev_reg_id}' at timestamp {timestamp}" ) @@ -535,7 +547,7 @@ def get_rev_state(cred_id: str, detail: dict): credential_definitions.values(), ) except CredxError as err: - raise IndyHolderError("Error creating presentation") from err + raise AnonCredsHolderError("Error creating presentation") from err return presentation.to_json() @@ -572,5 +584,5 @@ async def create_revocation_state( tails_file_path, ) except CredxError as err: - raise IndyHolderError("Error creating revocation state") from err + raise AnonCredsHolderError("Error creating revocation state") from err return rev_state.to_json() diff --git a/aries_cloudagent/anoncreds/credx/issuer.py b/aries_cloudagent/anoncreds/credx/issuer.py index b4a4524948..86ef68ee85 100644 --- a/aries_cloudagent/anoncreds/credx/issuer.py +++ b/aries_cloudagent/anoncreds/credx/issuer.py @@ -22,8 +22,8 @@ from ...askar.profile import AskarProfile from ..issuer import ( - IndyIssuer, - IndyIssuerError, + AnonCredsIssuer, + AnonCredsIssuerError, IndyIssuerRevocationRegistryFullError, DEFAULT_CRED_DEF_TAG, DEFAULT_SIGNATURE_TYPE, @@ -42,7 +42,7 @@ CATEGORY_REV_REG_ISSUER = "revocation_reg_def_issuer" -class IndyCredxIssuer(IndyIssuer): +class IndyCredxIssuer(AnonCredsIssuer): """Indy-Credx issuer class.""" def __init__(self, profile: AskarProfile): @@ -89,9 +89,9 @@ async def create_schema( async with self._profile.session() as session: await session.handle.insert(CATEGORY_SCHEMA, schema_id, schema_json) except CredxError as err: - raise IndyIssuerError("Error creating schema") from err + raise AnonCredsIssuerError("Error creating schema") from err except AskarError as err: - raise IndyIssuerError("Error storing schema") from err + raise AnonCredsIssuerError("Error storing schema") from err return (schema_id, schema_json) async def credential_definition_in_wallet( @@ -111,7 +111,9 @@ async def credential_definition_in_wallet( ) ) is not None except AskarError as err: - raise IndyIssuerError("Error checking for credential definition") from err + raise AnonCredsIssuerError( + "Error checking for credential definition" + ) from err async def create_and_store_credential_definition( self, @@ -153,7 +155,7 @@ async def create_and_store_credential_definition( cred_def_id = cred_def.id cred_def_json = cred_def.to_json() except CredxError as err: - raise IndyIssuerError("Error creating credential definition") from err + raise AnonCredsIssuerError("Error creating credential definition") from err try: async with self._profile.transaction() as txn: await txn.handle.insert( @@ -173,7 +175,7 @@ async def create_and_store_credential_definition( ) await txn.commit() except AskarError as err: - raise IndyIssuerError("Error storing credential definition") from err + raise AnonCredsIssuerError("Error storing credential definition") from err return (cred_def_id, cred_def_json) async def create_credential_offer(self, credential_definition_id: str) -> str: @@ -196,9 +198,11 @@ async def create_credential_offer(self, credential_definition_id: str) -> str: CATEGORY_CRED_DEF_KEY_PROOF, credential_definition_id ) except AskarError as err: - raise IndyIssuerError("Error retrieving credential definition") from err + raise AnonCredsIssuerError( + "Error retrieving credential definition" + ) from err if not cred_def or not key_proof: - raise IndyIssuerError( + raise AnonCredsIssuerError( "Credential definition not found for credential offer" ) try: @@ -213,7 +217,7 @@ async def create_credential_offer(self, credential_definition_id: str) -> str: key_proof.raw_value, ) except CredxError as err: - raise IndyIssuerError("Error creating credential offer") from err + raise AnonCredsIssuerError("Error creating credential offer") from err return credential_offer.to_json() @@ -251,9 +255,11 @@ async def create_credential( CATEGORY_CRED_DEF_PRIVATE, credential_definition_id ) except AskarError as err: - raise IndyIssuerError("Error retrieving credential definition") from err + raise AnonCredsIssuerError( + "Error retrieving credential definition" + ) from err if not cred_def or not cred_def_private: - raise IndyIssuerError( + raise AnonCredsIssuerError( "Credential definition not found for credential issuance" ) @@ -265,7 +271,7 @@ async def create_credential( try: credential_value = credential_values[attribute] except KeyError: - raise IndyIssuerError( + raise AnonCredsIssuerError( "Provided credential values are missing a value " f"for the schema attribute '{attribute}'" ) @@ -286,15 +292,17 @@ async def create_credential( CATEGORY_REV_REG_DEF_PRIVATE, revoc_reg_id ) if not rev_reg: - raise IndyIssuerError("Revocation registry not found") + raise AnonCredsIssuerError("Revocation registry not found") if not rev_reg_info: - raise IndyIssuerError("Revocation registry metadata not found") + raise AnonCredsIssuerError( + "Revocation registry metadata not found" + ) if not rev_reg_def: - raise IndyIssuerError( + raise AnonCredsIssuerError( "Revocation registry definition not found" ) if not rev_key: - raise IndyIssuerError( + raise AnonCredsIssuerError( "Revocation registry definition private data not found" ) # NOTE: we increment the index ahead of time to keep the @@ -309,7 +317,7 @@ async def create_credential( rev_reg_def.raw_value ) except CredxError as err: - raise IndyIssuerError( + raise AnonCredsIssuerError( "Error loading revocation registry definition" ) from err if rev_reg_index > rev_reg_def.max_cred_num: @@ -322,7 +330,7 @@ async def create_credential( ) await txn.commit() except AskarError as err: - raise IndyIssuerError( + raise AnonCredsIssuerError( "Error updating revocation registry index" ) from err @@ -356,7 +364,7 @@ async def create_credential( revoc, ) except CredxError as err: - raise IndyIssuerError("Error creating credential") from err + raise AnonCredsIssuerError("Error creating credential") from err return credential.to_json(), credential_revocation_id @@ -387,7 +395,9 @@ async def revoke_credentials( while True: attempt += 1 if attempt >= max_attempt: - raise IndyIssuerError("Repeated conflict attempting to update registry") + raise AnonCredsIssuerError( + "Repeated conflict attempting to update registry" + ) try: async with self._profile.session() as session: rev_reg_def = await session.handle.fetch( @@ -398,18 +408,22 @@ async def revoke_credentials( CATEGORY_REV_REG_INFO, revoc_reg_id ) if not rev_reg_def: - raise IndyIssuerError("Revocation registry definition not found") + raise AnonCredsIssuerError( + "Revocation registry definition not found" + ) if not rev_reg: - raise IndyIssuerError("Revocation registry not found") + raise AnonCredsIssuerError("Revocation registry not found") if not rev_reg_info: - raise IndyIssuerError("Revocation registry metadata not found") + raise AnonCredsIssuerError("Revocation registry metadata not found") except AskarError as err: - raise IndyIssuerError("Error retrieving revocation registry") from err + raise AnonCredsIssuerError( + "Error retrieving revocation registry" + ) from err try: rev_reg_def = RevocationRegistryDefinition.load(rev_reg_def.raw_value) except CredxError as err: - raise IndyIssuerError( + raise AnonCredsIssuerError( "Error loading revocation registry definition" ) from err @@ -454,7 +468,7 @@ async def revoke_credentials( try: rev_reg = RevocationRegistry.load(rev_reg.raw_value) except CredxError as err: - raise IndyIssuerError("Error loading revocation registry") from err + raise AnonCredsIssuerError("Error loading revocation registry") from err try: delta = await asyncio.get_event_loop().run_in_executor( @@ -467,7 +481,9 @@ async def revoke_credentials( ), ) except CredxError as err: - raise IndyIssuerError("Error updating revocation registry") from err + raise AnonCredsIssuerError( + "Error updating revocation registry" + ) from err try: async with self._profile.transaction() as txn: @@ -498,7 +514,7 @@ async def revoke_credentials( ) await txn.commit() except AskarError as err: - raise IndyIssuerError("Error saving revocation registry") from err + raise AnonCredsIssuerError("Error saving revocation registry") from err break return ( @@ -527,7 +543,7 @@ def update(d1, d2): delta.update_with(d2) return delta.to_json() except CredxError as err: - raise IndyIssuerError( + raise AnonCredsIssuerError( "Error merging revocation registry deltas" ) from err @@ -564,9 +580,11 @@ async def create_and_store_revocation_registry( async with self._profile.session() as session: cred_def = await session.handle.fetch(CATEGORY_CRED_DEF, cred_def_id) except AskarError as err: - raise IndyIssuerError("Error retrieving credential definition") from err + raise AnonCredsIssuerError( + "Error retrieving credential definition" + ) from err if not cred_def: - raise IndyIssuerError( + raise AnonCredsIssuerError( "Credential definition not found for revocation registry" ) @@ -588,7 +606,7 @@ async def create_and_store_revocation_registry( ), ) except CredxError as err: - raise IndyIssuerError("Error creating revocation registry") from err + raise AnonCredsIssuerError("Error creating revocation registry") from err rev_reg_def_id = rev_reg_def.id rev_reg_def_json = rev_reg_def.to_json() @@ -612,7 +630,7 @@ async def create_and_store_revocation_registry( ) await txn.commit() except AskarError as err: - raise IndyIssuerError("Error saving new revocation registry") from err + raise AnonCredsIssuerError("Error saving new revocation registry") from err return ( rev_reg_def_id, diff --git a/aries_cloudagent/anoncreds/credx/verifier.py b/aries_cloudagent/anoncreds/credx/verifier.py index e625076ecd..941f4092dd 100644 --- a/aries_cloudagent/anoncreds/credx/verifier.py +++ b/aries_cloudagent/anoncreds/credx/verifier.py @@ -7,12 +7,12 @@ from ...core.profile import Profile -from ..verifier import IndyVerifier, PresVerifyMsg +from ..verifier import AnonCredsVerifier, PresVerifyMsg LOGGER = logging.getLogger(__name__) -class IndyCredxVerifier(IndyVerifier): +class IndyCredxVerifier(AnonCredsVerifier): """Indy-Credx verifier class.""" def __init__(self, profile: Profile): diff --git a/aries_cloudagent/anoncreds/holder.py b/aries_cloudagent/anoncreds/holder.py index 5cd9a2a249..f2923e0fbc 100644 --- a/aries_cloudagent/anoncreds/holder.py +++ b/aries_cloudagent/anoncreds/holder.py @@ -7,11 +7,11 @@ from ..ledger.base import BaseLedger -class IndyHolderError(BaseError): +class AnonCredsHolderError(BaseError): """Base class for holder exceptions.""" -class IndyHolder(ABC, metaclass=ABCMeta): +class AnonCredsHolder(ABC, metaclass=ABCMeta): """Base class for holder.""" RECORD_TYPE_MIME_TYPES = "attribute-mime-types" diff --git a/aries_cloudagent/anoncreds/issuer.py b/aries_cloudagent/anoncreds/issuer.py index d889746d41..e3669f451d 100644 --- a/aries_cloudagent/anoncreds/issuer.py +++ b/aries_cloudagent/anoncreds/issuer.py @@ -10,15 +10,15 @@ DEFAULT_SIGNATURE_TYPE = "CL" -class IndyIssuerError(BaseError): +class AnonCredsIssuerError(BaseError): """Generic issuer error.""" -class IndyIssuerRevocationRegistryFullError(IndyIssuerError): +class AnonCredsIssuerRevocationRegistryFullError(AnonCredsIssuerError): """Revocation registry is full when issuing a new credential.""" -class IndyIssuer(ABC, metaclass=ABCMeta): +class AnonCredsIssuer(ABC, metaclass=ABCMeta): """Base class for Indy Issuer.""" def __repr__(self) -> str: diff --git a/aries_cloudagent/anoncreds/sdk/holder.py b/aries_cloudagent/anoncreds/sdk/holder.py index 48cb2abac0..42dab1dc34 100644 --- a/aries_cloudagent/anoncreds/sdk/holder.py +++ b/aries_cloudagent/anoncreds/sdk/holder.py @@ -17,7 +17,7 @@ from ...storage.record import StorageRecord from ...wallet.error import WalletNotFoundError -from ..holder import IndyHolder, IndyHolderError +from ..holder import AnonCredsHolder, AnonCredsHolderError from .error import IndyErrorHandler from .util import create_tails_reader @@ -25,7 +25,7 @@ LOGGER = logging.getLogger(__name__) -class IndySdkHolder(IndyHolder): +class IndySdkHolder(AnonCredsHolder): """Indy-SDK holder implementation.""" def __init__(self, wallet: IndyOpenWallet): @@ -55,7 +55,7 @@ async def create_credential_request( """ with IndyErrorHandler( - "Error when creating credential request", IndyHolderError + "Error when creating credential request", AnonCredsHolderError ): ( credential_request_json, @@ -104,7 +104,7 @@ async def store_credential( """ with IndyErrorHandler( - "Error when storing credential in wallet", IndyHolderError + "Error when storing credential in wallet", AnonCredsHolderError ): credential_id = await indy.anoncreds.prover_store_credential( wallet_handle=self.wallet.handle, @@ -123,10 +123,10 @@ async def store_credential( } if mime_types: record = StorageRecord( - type=IndyHolder.RECORD_TYPE_MIME_TYPES, + type=AnonCredsHolder.RECORD_TYPE_MIME_TYPES, value=credential_id, tags=mime_types, - id=f"{IndyHolder.RECORD_TYPE_MIME_TYPES}::{credential_id}", + id=f"{AnonCredsHolder.RECORD_TYPE_MIME_TYPES}::{credential_id}", ) indy_stor = IndySdkStorage(self.wallet) await indy_stor.add_record(record) @@ -147,11 +147,11 @@ async def get_credentials(self, start: int, count: int, wql: dict): async def fetch(limit): """Fetch up to limit (default smaller of all remaining or 256) creds.""" creds = [] - CHUNK = min(record_count, limit or record_count, IndyHolder.CHUNK) + CHUNK = min(record_count, limit or record_count, AnonCredsHolder.CHUNK) cardinality = min(limit or record_count, record_count) with IndyErrorHandler( - "Error fetching credentials from wallet", IndyHolderError + "Error fetching credentials from wallet", AnonCredsHolderError ): while len(creds) < cardinality: batch = json.loads( @@ -165,7 +165,7 @@ async def fetch(limit): return creds with IndyErrorHandler( - "Error when constructing wallet credential query", IndyHolderError + "Error when constructing wallet credential query", AnonCredsHolderError ): ( search_handle, @@ -206,11 +206,11 @@ async def get_credentials_for_presentation_request_by_referent( async def fetch(reft, limit): """Fetch up to limit (default smaller of all remaining or 256) creds.""" creds = [] - CHUNK = min(IndyHolder.CHUNK, limit or IndyHolder.CHUNK) + CHUNK = min(AnonCredsHolder.CHUNK, limit or AnonCredsHolder.CHUNK) with IndyErrorHandler( "Error fetching credentials from wallet for presentation request", - IndyHolderError, + AnonCredsHolderError, ): while not limit or len(creds) < limit: batch = json.loads( @@ -224,7 +224,7 @@ async def fetch(reft, limit): return creds with IndyErrorHandler( - "Error when constructing wallet credential query", IndyHolderError + "Error when constructing wallet credential query", AnonCredsHolderError ): search_handle = await ( indy.anoncreds.prover_search_credentials_for_proof_req( @@ -303,7 +303,7 @@ async def get_credential(self, credential_id: str) -> str: raise IndyErrorHandler.wrap_error( err, f"Error when fetching credential {credential_id}", - IndyHolderError, + AnonCredsHolderError, ) from err return credential_json @@ -344,8 +344,8 @@ async def delete_credential(self, credential_id: str): try: indy_stor = IndySdkStorage(self.wallet) mime_types_record = await indy_stor.get_record( - IndyHolder.RECORD_TYPE_MIME_TYPES, - f"{IndyHolder.RECORD_TYPE_MIME_TYPES}::{credential_id}", + AnonCredsHolder.RECORD_TYPE_MIME_TYPES, + f"{AnonCredsHolder.RECORD_TYPE_MIME_TYPES}::{credential_id}", ) await indy_stor.delete_record(mime_types_record) except StorageNotFoundError: @@ -364,7 +364,7 @@ async def delete_credential(self, credential_id: str): ) else: raise IndyErrorHandler.wrap_error( - err, "Error when deleting credential", IndyHolderError + err, "Error when deleting credential", AnonCredsHolderError ) from err async def get_mime_type( @@ -383,8 +383,8 @@ async def get_mime_type( """ try: mime_types_record = await IndySdkStorage(self.wallet).get_record( - IndyHolder.RECORD_TYPE_MIME_TYPES, - f"{IndyHolder.RECORD_TYPE_MIME_TYPES}::{credential_id}", + AnonCredsHolder.RECORD_TYPE_MIME_TYPES, + f"{AnonCredsHolder.RECORD_TYPE_MIME_TYPES}::{credential_id}", ) except StorageError: return None # no MIME types: not an error @@ -428,12 +428,12 @@ async def create_presentation( f"requested attribute {reft} names {named_attrs} " f"but restricts {restricted_attr} value" ) - raise IndyHolderError( + raise AnonCredsHolderError( f"Requested attribute {reft} names {named_attrs} " f"but restricts {restricted_attr} value" ) - with IndyErrorHandler("Error when constructing proof", IndyHolderError): + with IndyErrorHandler("Error when constructing proof", AnonCredsHolderError): presentation_json = await indy.anoncreds.prover_create_proof( self.wallet.handle, json.dumps(presentation_request), @@ -469,7 +469,7 @@ async def create_revocation_state( """ with IndyErrorHandler( - "Error when constructing revocation state", IndyHolderError + "Error when constructing revocation state", AnonCredsHolderError ): tails_file_reader = await create_tails_reader(tails_file_path) rev_state_json = await indy.anoncreds.create_revocation_state( diff --git a/aries_cloudagent/anoncreds/sdk/issuer.py b/aries_cloudagent/anoncreds/sdk/issuer.py index 3d84473c93..cd16fd67c9 100644 --- a/aries_cloudagent/anoncreds/sdk/issuer.py +++ b/aries_cloudagent/anoncreds/sdk/issuer.py @@ -13,8 +13,8 @@ from ...storage.error import StorageError from ..issuer import ( - IndyIssuer, - IndyIssuerError, + AnonCredsIssuer, + AnonCredsIssuerError, IndyIssuerRevocationRegistryFullError, DEFAULT_CRED_DEF_TAG, DEFAULT_SIGNATURE_TYPE, @@ -26,7 +26,7 @@ LOGGER = logging.getLogger(__name__) -class IndySdkIssuer(IndyIssuer): +class IndySdkIssuer(AnonCredsIssuer): """Indy-SDK issuer implementation.""" def __init__(self, profile: IndySdkProfile): @@ -60,7 +60,7 @@ async def create_schema( """ - with IndyErrorHandler("Error when creating schema", IndyIssuerError): + with IndyErrorHandler("Error when creating schema", AnonCredsIssuerError): schema_id, schema_json = await indy.anoncreds.issuer_create_schema( origin_did, schema_name, @@ -91,7 +91,7 @@ async def credential_definition_in_wallet( raise IndyErrorHandler.wrap_error( err, "Error when checking wallet for credential definition", - IndyIssuerError, + AnonCredsIssuerError, ) from err # recognized error signifies no such cred def in wallet: pass return False @@ -120,7 +120,7 @@ async def create_and_store_credential_definition( """ with IndyErrorHandler( - "Error when creating credential definition", IndyIssuerError + "Error when creating credential definition", AnonCredsIssuerError ): ( credential_definition_id, @@ -147,7 +147,7 @@ async def create_credential_offer(self, credential_definition_id: str) -> str: """ with IndyErrorHandler( - "Exception when creating credential offer", IndyIssuerError + "Exception when creating credential offer", AnonCredsIssuerError ): credential_offer_json = await indy.anoncreds.issuer_create_credential_offer( self.profile.wallet.handle, credential_definition_id @@ -188,7 +188,7 @@ async def create_credential( try: credential_value = credential_values[attribute] except KeyError: - raise IndyIssuerError( + raise AnonCredsIssuerError( "Provided credential values are missing a value " + f"for the schema attribute '{attribute}'" ) @@ -226,7 +226,7 @@ async def create_credential( ) except IndyError as err: raise IndyErrorHandler.wrap_error( - err, "Error when issuing credential", IndyIssuerError + err, "Error when issuing credential", AnonCredsIssuerError ) from err except StorageError as err: LOGGER.warning( @@ -266,7 +266,7 @@ async def revoke_credentials( result_json = None for cred_rev_id in set(cred_rev_ids): with IndyErrorHandler( - "Exception when revoking credential", IndyIssuerError + "Exception when revoking credential", AnonCredsIssuerError ): try: delta_json = await indy.anoncreds.issuer_revoke_credential( @@ -289,7 +289,7 @@ async def revoke_credentials( else: LOGGER.error( IndyErrorHandler.wrap_error( - err, "Revocation error", IndyIssuerError + err, "Revocation error", AnonCredsIssuerError ).roll_up ) failed_crids.add(int(cred_rev_id)) @@ -362,7 +362,7 @@ async def create_and_store_revocation_registry( tails_writer = await create_tails_writer(tails_base_path) with IndyErrorHandler( - "Exception when creating revocation registry", IndyIssuerError + "Exception when creating revocation registry", AnonCredsIssuerError ): ( rev_reg_id, diff --git a/aries_cloudagent/anoncreds/sdk/profile.py b/aries_cloudagent/anoncreds/sdk/profile.py index 525e52917b..b1bd27d663 100644 --- a/aries_cloudagent/anoncreds/sdk/profile.py +++ b/aries_cloudagent/anoncreds/sdk/profile.py @@ -17,9 +17,9 @@ from ...wallet.base import BaseWallet from ...wallet.indy import IndySdkWallet -from ..holder import IndyHolder +from ..holder import AnonCredsHolder from ..issuer import IndyIssuer -from ..verifier import IndyVerifier +from ..verifier import AnonCredsVerifier from .wallet_setup import IndyWalletConfig, IndyOpenWallet @@ -73,7 +73,7 @@ def bind_providers(self): ) injector.bind_provider( - IndyHolder, + AnonCredsHolder, ClassProvider( "aries_cloudagent.indy.sdk.holder.IndySdkHolder", self.opened ), @@ -96,7 +96,7 @@ def bind_providers(self): ) if self.ledger_pool or self.settings.get("ledger.ledger_config_list"): injector.bind_provider( - IndyVerifier, + AnonCredsVerifier, ClassProvider( "aries_cloudagent.indy.sdk.verifier.IndySdkVerifier", ref(self), diff --git a/aries_cloudagent/anoncreds/sdk/tests/test_holder.py b/aries_cloudagent/anoncreds/sdk/tests/test_holder.py index c67cfd375c..8403efcd49 100644 --- a/aries_cloudagent/anoncreds/sdk/tests/test_holder.py +++ b/aries_cloudagent/anoncreds/sdk/tests/test_holder.py @@ -7,7 +7,7 @@ from indy.error import IndyError, ErrorCode -from ...holder import IndyHolder, IndyHolderError +from ...holder import AnonCredsHolder, AnonCredsHolderError from .. import holder as test_module @@ -104,7 +104,7 @@ async def test_get_credential_attrs_mime_types(self, mock_nonsec_get_wallet_reco cred_id = "credential_id" dummy_tags = {"a": "1", "b": "2"} dummy_rec = { - "type": IndyHolder.RECORD_TYPE_MIME_TYPES, + "type": AnonCredsHolder.RECORD_TYPE_MIME_TYPES, "id": cred_id, "value": "value", "tags": dummy_tags, @@ -116,7 +116,7 @@ async def test_get_credential_attrs_mime_types(self, mock_nonsec_get_wallet_reco mock_nonsec_get_wallet_record.assert_called_once_with( self.wallet.handle, dummy_rec["type"], - f"{IndyHolder.RECORD_TYPE_MIME_TYPES}::{dummy_rec['id']}", + f"{AnonCredsHolder.RECORD_TYPE_MIME_TYPES}::{dummy_rec['id']}", json.dumps( {"retrieveType": False, "retrieveValue": True, "retrieveTags": True} ), @@ -129,7 +129,7 @@ async def test_get_credential_attr_mime_type(self, mock_nonsec_get_wallet_record cred_id = "credential_id" dummy_tags = {"a": "1", "b": "2"} dummy_rec = { - "type": IndyHolder.RECORD_TYPE_MIME_TYPES, + "type": AnonCredsHolder.RECORD_TYPE_MIME_TYPES, "id": cred_id, "value": "value", "tags": dummy_tags, @@ -141,7 +141,7 @@ async def test_get_credential_attr_mime_type(self, mock_nonsec_get_wallet_record mock_nonsec_get_wallet_record.assert_called_once_with( self.wallet.handle, dummy_rec["type"], - f"{IndyHolder.RECORD_TYPE_MIME_TYPES}::{dummy_rec['id']}", + f"{AnonCredsHolder.RECORD_TYPE_MIME_TYPES}::{dummy_rec['id']}", json.dumps( {"retrieveType": False, "retrieveValue": True, "retrieveTags": True} ), @@ -154,7 +154,7 @@ async def test_get_credential_attr_mime_type_x(self, mock_nonsec_get_wallet_reco cred_id = "credential_id" dummy_tags = {"a": "1", "b": "2"} dummy_rec = { - "type": IndyHolder.RECORD_TYPE_MIME_TYPES, + "type": AnonCredsHolder.RECORD_TYPE_MIME_TYPES, "id": cred_id, "value": "value", "tags": dummy_tags, @@ -235,18 +235,18 @@ async def test_get_credentials_for_presentation_request_by_reft( "rev_reg_id": None if i % 2 else "dummy-rrid", } } - for i in range(test_module.IndyHolder.CHUNK) + for i in range(test_module.AnonCredsHolder.CHUNK) ] ), json.dumps( [ { "cred_info": { - "referent": f"reft-{test_module.IndyHolder.CHUNK + i}", + "referent": f"reft-{test_module.AnonCredsHolder.CHUNK + i}", "rev_reg_id": None, } } - for i in range(SIZE % test_module.IndyHolder.CHUNK) + for i in range(SIZE % test_module.AnonCredsHolder.CHUNK) ] ), ] @@ -280,12 +280,12 @@ async def test_get_credentials_for_presentation_request_by_reft( assert all( not c["cred_info"]["rev_reg_id"] for c in credentials[ - 0 : len(credentials) - (test_module.IndyHolder.CHUNK // 2) + 0 : len(credentials) - (test_module.AnonCredsHolder.CHUNK // 2) ] ) # irrevocable first assert all( c["cred_info"]["rev_reg_id"] - for c in credentials[-test_module.IndyHolder.CHUNK // 2 :] + for c in credentials[-test_module.AnonCredsHolder.CHUNK // 2 :] ) # revocable last @async_mock.patch("indy.anoncreds.prover_search_credentials_for_proof_req") @@ -341,7 +341,7 @@ async def test_get_credential_not_found(self, mock_get_cred): async def test_get_credential_x(self, mock_get_cred): mock_get_cred.side_effect = IndyError("unexpected failure") - with self.assertRaises(test_module.IndyHolderError): + with self.assertRaises(test_module.AnonCredsHolderError): await self.holder.get_credential("credential_id") async def test_credential_revoked(self): @@ -455,7 +455,7 @@ async def test_delete_credential_x( mock_prover_del_cred.side_effect = IndyError( error_code=ErrorCode.CommonInvalidParam1 ) - with self.assertRaises(test_module.IndyHolderError): + with self.assertRaises(test_module.AnonCredsHolderError): await self.holder.delete_credential("credential_id") assert mock_prover_del_cred.call_count == 2 @@ -562,7 +562,7 @@ async def test_create_presentation_restr_attr_mismatch_x(self): ] for proof_req in PROOF_REQS: - with self.assertRaises(IndyHolderError): + with self.assertRaises(AnonCredsHolderError): await self.holder.create_presentation( proof_req, "requested_credentials", diff --git a/aries_cloudagent/anoncreds/sdk/tests/test_issuer.py b/aries_cloudagent/anoncreds/sdk/tests/test_issuer.py index 3b007e788e..4b847f29ae 100644 --- a/aries_cloudagent/anoncreds/sdk/tests/test_issuer.py +++ b/aries_cloudagent/anoncreds/sdk/tests/test_issuer.py @@ -16,7 +16,7 @@ from ....wallet.indy import IndySdkWallet from ....ledger.indy import IndySdkLedgerPool -from ...issuer import IndyIssuerRevocationRegistryFullError +from ...issuer import AnonCredsIssuerRevocationRegistryFullError from .. import issuer as test_module @@ -110,7 +110,7 @@ async def test_credential_definition_in_wallet_x(self, mock_indy_create_offer): mock_indy_create_offer.side_effect = IndyError( error_code=ErrorCode.WalletInvalidHandle ) - with self.assertRaises(test_module.IndyIssuerError): + with self.assertRaises(test_module.AnonCredsIssuerError): await self.issuer.credential_definition_in_wallet(CRED_DEF_ID) @async_mock.patch("indy.anoncreds.issuer_create_credential_offer") @@ -167,7 +167,7 @@ async def test_create_revoke_credentials( for cr_id in test_cred_rev_ids ] - with self.assertRaises(test_module.IndyIssuerError): # missing attribute + with self.assertRaises(test_module.AnonCredsIssuerError): # missing attribute cred_json, revoc_id = await self.issuer.create_credential( test_schema, test_offer, @@ -249,7 +249,7 @@ async def test_create_revoke_credentials_x( for cr_id in test_cred_rev_ids ] - with self.assertRaises(test_module.IndyIssuerError): # missing attribute + with self.assertRaises(test_module.AnonCredsIssuerError): # missing attribute cred_json, revoc_id = await self.issuer.create_credential( test_schema, test_offer, @@ -326,7 +326,7 @@ async def test_create_credential_rr_full( error_code=ErrorCode.AnoncredsRevocationRegistryFullError ) - with self.assertRaises(IndyIssuerRevocationRegistryFullError): + with self.assertRaises(AnonCredsIssuerRevocationRegistryFullError): await self.issuer.create_credential( test_schema, test_offer, @@ -358,7 +358,7 @@ async def test_create_credential_x_indy( error_code=ErrorCode.WalletInvalidHandle ) - with self.assertRaises(test_module.IndyIssuerError): + with self.assertRaises(test_module.AnonCredsIssuerError): await self.issuer.create_credential( test_schema, test_offer, diff --git a/aries_cloudagent/anoncreds/sdk/verifier.py b/aries_cloudagent/anoncreds/sdk/verifier.py index 5c67463eed..09820fe649 100644 --- a/aries_cloudagent/anoncreds/sdk/verifier.py +++ b/aries_cloudagent/anoncreds/sdk/verifier.py @@ -8,12 +8,12 @@ from ...core.profile import Profile -from ..verifier import IndyVerifier, PresVerifyMsg +from ..verifier import AnonCredsVerifier, PresVerifyMsg LOGGER = logging.getLogger(__name__) -class IndySdkVerifier(IndyVerifier): +class IndySdkVerifier(AnonCredsVerifier): """Indy-SDK verifier implementation.""" def __init__(self, profile: Profile): diff --git a/aries_cloudagent/anoncreds/tests/test_verifier.py b/aries_cloudagent/anoncreds/tests/test_verifier.py index 90b75b92a6..8815f79a9e 100644 --- a/aries_cloudagent/anoncreds/tests/test_verifier.py +++ b/aries_cloudagent/anoncreds/tests/test_verifier.py @@ -14,7 +14,7 @@ from ...multitenant.manager import MultitenantManager from .. import verifier as test_module -from ..verifier import IndyVerifier +from ..verifier import AnonCredsVerifier INDY_PROOF_REQ_NAME = { @@ -293,7 +293,7 @@ } -class MockVerifier(IndyVerifier): +class MockVerifier(AnonCredsVerifier): async def verify_presentation( self, pres_req, diff --git a/aries_cloudagent/anoncreds/verifier.py b/aries_cloudagent/anoncreds/verifier.py index 9a83ce5b8f..5d4203e46b 100644 --- a/aries_cloudagent/anoncreds/verifier.py +++ b/aries_cloudagent/anoncreds/verifier.py @@ -32,7 +32,7 @@ class PresVerifyMsg(str, Enum): PRES_VERIFY_ERROR = "VERIFY_ERROR" -class IndyVerifier(ABC, metaclass=ABCMeta): +class AnonCredsVerifier(ABC, metaclass=ABCMeta): """Base class for Indy Verifier.""" def __repr__(self) -> str: diff --git a/aries_cloudagent/askar/profile.py b/aries_cloudagent/askar/profile.py index 3a31a73859..c6e8fa6361 100644 --- a/aries_cloudagent/askar/profile.py +++ b/aries_cloudagent/askar/profile.py @@ -16,9 +16,9 @@ from ..config.provider import ClassProvider from ..core.error import ProfileError from ..core.profile import Profile, ProfileManager, ProfileSession -from ..anoncreds.holder import IndyHolder +from ..anoncreds.holder import AnonCredsHolder from ..anoncreds.issuer import IndyIssuer -from ..anoncreds.verifier import IndyVerifier +from ..anoncreds.verifier import AnonCredsVerifier from ..ledger.base import BaseLedger from ..ledger.indy_vdr import IndyVdrLedger, IndyVdrLedgerPool from ..storage.base import BaseStorage, BaseStorageSearch @@ -101,7 +101,7 @@ def bind_providers(self): ) injector.bind_provider( - IndyHolder, + AnonCredsHolder, ClassProvider( "aries_cloudagent.indy.credx.holder.IndyCredxHolder", ref(self), @@ -127,7 +127,7 @@ def bind_providers(self): ) if self.ledger_pool or self.settings.get("ledger.ledger_config_list"): injector.bind_provider( - IndyVerifier, + AnonCredsVerifier, ClassProvider( "aries_cloudagent.indy.credx.verifier.IndyCredxVerifier", ref(self), diff --git a/aries_cloudagent/core/conductor.py b/aries_cloudagent/core/conductor.py index 25714b7048..022b131b25 100644 --- a/aries_cloudagent/core/conductor.py +++ b/aries_cloudagent/core/conductor.py @@ -27,7 +27,7 @@ from ..config.provider import ClassProvider from ..config.wallet import wallet_config from ..core.profile import Profile -from ..anoncreds.verifier import IndyVerifier +from ..anoncreds.verifier import AnonCredsVerifier from ..ledger.error import LedgerConfigError, LedgerTransactionError from ..ledger.multiple_ledger.base_manager import ( @@ -145,7 +145,7 @@ async def setup(self): and ledger.BACKEND_NAME == "indy-vdr" ): context.injector.bind_provider( - IndyVerifier, + AnonCredsVerifier, ClassProvider( "aries_cloudagent.indy.credx.verifier.IndyCredxVerifier", self.root_profile, @@ -156,7 +156,7 @@ async def setup(self): and ledger.BACKEND_NAME == "indy" ): context.injector.bind_provider( - IndyVerifier, + AnonCredsVerifier, ClassProvider( "aries_cloudagent.indy.sdk.verifier.IndySdkVerifier", self.root_profile, diff --git a/aries_cloudagent/holder/routes.py b/aries_cloudagent/holder/routes.py index cf824655f7..e05cfec6a1 100644 --- a/aries_cloudagent/holder/routes.py +++ b/aries_cloudagent/holder/routes.py @@ -13,7 +13,7 @@ from marshmallow import fields from ..admin.request_context import AdminRequestContext -from ..anoncreds.holder import IndyHolder, IndyHolderError +from ..anoncreds.holder import AnonCredsHolder, AnonCredsHolderError from ..anoncreds.models.cred_precis import IndyCredInfoSchema from ..ledger.base import BaseLedger from ..ledger.error import LedgerError @@ -175,7 +175,7 @@ async def credentials_get(request: web.BaseRequest): context: AdminRequestContext = request["context"] credential_id = request.match_info["credential_id"] - holder = context.profile.inject(IndyHolder) + holder = context.profile.inject(AnonCredsHolder) try: credential = await holder.get_credential(credential_id) except WalletNotFoundError as err: @@ -215,7 +215,7 @@ async def credentials_revoked(request: web.BaseRequest): async with ledger: try: - holder = session.inject(IndyHolder) + holder = session.inject(AnonCredsHolder) revoked = await holder.credential_revoked( ledger, credential_id, @@ -248,7 +248,7 @@ async def credentials_attr_mime_types_get(request: web.BaseRequest): credential_id = request.match_info["credential_id"] async with context.profile.session() as session: - holder = session.inject(IndyHolder) + holder = session.inject(AnonCredsHolder) mime_types = await holder.get_mime_type(credential_id) return web.json_response({"results": mime_types}) @@ -272,7 +272,7 @@ async def credentials_remove(request: web.BaseRequest): try: async with context.profile.session() as session: - holder = session.inject(IndyHolder) + holder = session.inject(AnonCredsHolder) await holder.delete_credential(credential_id) topic = "acapy::record::credential::delete" await context.profile.notify(topic, {"id": credential_id, "state": "deleted"}) @@ -312,10 +312,10 @@ async def credentials_list(request: web.BaseRequest): count = int(count) if isinstance(count, str) else 10 async with context.profile.session() as session: - holder = session.inject(IndyHolder) + holder = session.inject(AnonCredsHolder) try: credentials = await holder.get_credentials(start, count, wql) - except IndyHolderError as err: + except AnonCredsHolderError as err: raise web.HTTPBadRequest(reason=err.roll_up) from err return web.json_response({"results": credentials}) diff --git a/aries_cloudagent/holder/tests/test_routes.py b/aries_cloudagent/holder/tests/test_routes.py index edef758fba..e4edbc723a 100644 --- a/aries_cloudagent/holder/tests/test_routes.py +++ b/aries_cloudagent/holder/tests/test_routes.py @@ -214,7 +214,7 @@ async def test_credentials_list_x_holder(self): IndyHolder, async_mock.MagicMock( get_credentials=async_mock.CoroutineMock( - side_effect=test_module.IndyHolderError() + side_effect=test_module.AnonCredsHolderError() ) ), ) diff --git a/aries_cloudagent/ledger/base.py b/aries_cloudagent/ledger/base.py index b3f0604104..4948970992 100644 --- a/aries_cloudagent/ledger/base.py +++ b/aries_cloudagent/ledger/base.py @@ -9,7 +9,11 @@ from hashlib import sha256 from typing import List, Sequence, Tuple, Union -from ..anoncreds.issuer import DEFAULT_CRED_DEF_TAG, IndyIssuer, IndyIssuerError +from ..anoncreds.issuer import ( + DEFAULT_CRED_DEF_TAG, + AnonCredsIssuer, + AnonCredsIssuerError, +) from ..utils import sentinel from ..wallet.did_info import DIDInfo @@ -273,7 +277,7 @@ async def check_existing_schema( async def create_and_send_schema( self, - issuer: IndyIssuer, + issuer: AnonCredsIssuer, schema_name: str, schema_version: str, attribute_names: Sequence[str], @@ -314,7 +318,7 @@ async def create_and_send_schema( schema_version, attribute_names, ) - except IndyIssuerError as err: + except AnonCredsIssuerError as err: raise LedgerError(err.message) from err schema_def = json.loads(schema_json) @@ -405,7 +409,7 @@ async def send_revoc_reg_entry( async def create_and_send_credential_definition( self, - issuer: IndyIssuer, + issuer: AnonCredsIssuer, schema_id: str, signature_type: str = None, tag: str = None, @@ -463,7 +467,7 @@ async def create_and_send_credential_definition( f"ledger {self.pool_name} but not in wallet " f"{self.profile.name}" ) - except IndyIssuerError as err: + except AnonCredsIssuerError as err: raise LedgerError(err.message) from err credential_definition_json = json.dumps(ledger_cred_def) @@ -478,7 +482,7 @@ async def create_and_send_credential_definition( f"wallet {self.profile.name} but not on ledger " f"{self.pool.name}" ) - except IndyIssuerError as err: + except AnonCredsIssuerError as err: raise LedgerError(err.message) from err # Cred def is neither on ledger nor in wallet: create and send it @@ -494,7 +498,7 @@ async def create_and_send_credential_definition( tag, support_revocation, ) - except IndyIssuerError as err: + except AnonCredsIssuerError as err: raise LedgerError(err.message) from err if await self.is_ledger_read_only(): diff --git a/aries_cloudagent/revocation/manager.py b/aries_cloudagent/revocation/manager.py index 334f5a4d33..ca891123bb 100644 --- a/aries_cloudagent/revocation/manager.py +++ b/aries_cloudagent/revocation/manager.py @@ -9,7 +9,7 @@ ) from ..core.error import BaseError from ..core.profile import Profile -from ..anoncreds.issuer import IndyIssuer +from ..anoncreds.issuer import AnonCredsIssuer from ..storage.error import StorageNotFoundError from .indy import IndyRevocation from .models.issuer_cred_rev_record import IssuerCredRevRecord @@ -107,7 +107,7 @@ async def revoke_credential( along with any revocations pending against it """ - issuer = self._profile.inject(IndyIssuer) + issuer = self._profile.inject(AnonCredsIssuer) revoc = IndyRevocation(self._profile) issuer_rr_rec = await revoc.get_issuer_rev_reg_record(rev_reg_id) @@ -208,7 +208,7 @@ async def publish_pending_revocations( Returns: mapping from each revocation registry id to its cred rev ids published. """ result = {} - issuer = self._profile.inject(IndyIssuer) + issuer = self._profile.inject(AnonCredsIssuer) async with self._profile.session() as session: issuer_rr_recs = await IssuerRevRegRecord.query_by_pending(session) diff --git a/aries_cloudagent/revocation/routes.py b/aries_cloudagent/revocation/routes.py index 6f2b17db9e..69f9230dee 100644 --- a/aries_cloudagent/revocation/routes.py +++ b/aries_cloudagent/revocation/routes.py @@ -22,7 +22,7 @@ from ..connections.models.conn_record import ConnRecord from ..core.event_bus import Event, EventBus from ..core.profile import Profile -from ..anoncreds.issuer import IndyIssuerError +from ..anoncreds.issuer import AnonCredsIssuerError from ..ledger.base import BaseLedger from ..ledger.multiple_ledger.base_manager import BaseMultipleLedgerManager from ..ledger.error import LedgerError @@ -473,7 +473,7 @@ async def revoke(request: web.BaseRequest): RevocationManagerError, RevocationError, StorageError, - IndyIssuerError, + AnonCredsIssuerError, LedgerError, ) as err: raise web.HTTPBadRequest(reason=err.roll_up) from err @@ -505,7 +505,7 @@ async def publish_revocations(request: web.BaseRequest): rev_reg_resp = await rev_manager.publish_pending_revocations( rrid2crid, ) - except (RevocationError, StorageError, IndyIssuerError, LedgerError) as err: + except (RevocationError, StorageError, AnonCredsIssuerError, LedgerError) as err: raise web.HTTPBadRequest(reason=err.roll_up) from err return web.json_response({"rrid2crid": rev_reg_resp}) @@ -828,7 +828,7 @@ async def update_rev_reg_revoked_state(request: web.BaseRequest): RevocationManagerError, RevocationError, StorageError, - IndyIssuerError, + AnonCredsIssuerError, LedgerError, ) as err: raise web.HTTPBadRequest(reason=err.roll_up) From e4c986a412403383fa926694c250a48b4ece5bb7 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Wed, 22 Feb 2023 13:30:22 -0500 Subject: [PATCH 004/150] feat: add anoncreds concrete classes, copyied from credx Signed-off-by: Daniel Bluhm --- .../anoncreds/anoncreds/__init__.py | 0 .../anoncreds/anoncreds/holder.py | 588 ++++++++++++++++ .../anoncreds/anoncreds/issuer.py | 639 ++++++++++++++++++ .../anoncreds/anoncreds/tests/__init__.py | 0 .../anoncreds/tests/test_cred_issuance.py | 341 ++++++++++ .../anoncreds/anoncreds/verifier.py | 85 +++ aries_cloudagent/anoncreds/credx/issuer.py | 4 +- 7 files changed, 1655 insertions(+), 2 deletions(-) create mode 100644 aries_cloudagent/anoncreds/anoncreds/__init__.py create mode 100644 aries_cloudagent/anoncreds/anoncreds/holder.py create mode 100644 aries_cloudagent/anoncreds/anoncreds/issuer.py create mode 100644 aries_cloudagent/anoncreds/anoncreds/tests/__init__.py create mode 100644 aries_cloudagent/anoncreds/anoncreds/tests/test_cred_issuance.py create mode 100644 aries_cloudagent/anoncreds/anoncreds/verifier.py diff --git a/aries_cloudagent/anoncreds/anoncreds/__init__.py b/aries_cloudagent/anoncreds/anoncreds/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aries_cloudagent/anoncreds/anoncreds/holder.py b/aries_cloudagent/anoncreds/anoncreds/holder.py new file mode 100644 index 0000000000..5ef3ae39e8 --- /dev/null +++ b/aries_cloudagent/anoncreds/anoncreds/holder.py @@ -0,0 +1,588 @@ +"""Indy holder implementation.""" + +import asyncio +import json +import logging +import re +import uuid + +from typing import Dict, Sequence, Tuple, Union + +from aries_askar import AskarError, AskarErrorCode +from anoncreds import ( + AnoncredsError, + Credential, + CredentialRequest, + CredentialRevocationState, + MasterSecret, + Presentation, + PresentCredentials, +) + +from ...askar.profile import AskarProfile +from ...ledger.base import BaseLedger +from ...wallet.error import WalletNotFoundError + +from ..holder import AnonCredsHolder, AnonCredsHolderError + +LOGGER = logging.getLogger(__name__) + +CATEGORY_CREDENTIAL = "credential" +CATEGORY_MASTER_SECRET = "master_secret" + + +def _make_cred_info(cred_id, cred: Credential): + cred_info = cred.to_dict() # not secure! + rev_info = cred_info["signature"]["r_credential"] + return { + "referent": cred_id, + "schema_id": cred_info["schema_id"], + "cred_def_id": cred_info["cred_def_id"], + "rev_reg_id": cred_info["rev_reg_id"], + "cred_rev_id": str(rev_info["i"]) if rev_info else None, + "attrs": {name: val["raw"] for (name, val) in cred_info["values"].items()}, + } + + +def _normalize_attr_name(name: str) -> str: + return name.replace(" ", "") + + +class IndyCredxHolder(AnonCredsHolder): + """Indy-credx holder class.""" + + MASTER_SECRET_ID = "default" + + def __init__(self, profile: AskarProfile): + """ + Initialize an IndyCredxHolder instance. + + Args: + profile: The active profile instance + + """ + self._profile = profile + + @property + def profile(self) -> AskarProfile: + """Accessor for the profile instance.""" + return self._profile + + async def get_master_secret(self) -> MasterSecret: + """Get or create the default master secret.""" + + while True: + async with self._profile.session() as session: + try: + record = await session.handle.fetch( + CATEGORY_MASTER_SECRET, IndyCredxHolder.MASTER_SECRET_ID + ) + except AskarError as err: + raise AnonCredsHolderError("Error fetching master secret") from err + if record: + try: + secret = MasterSecret.load(record.raw_value) + except AnoncredsError as err: + raise AnonCredsHolderError( + "Error loading master secret" + ) from err + break + else: + try: + secret = MasterSecret.create() + except AnoncredsError as err: + raise AnonCredsHolderError( + "Error creating master secret" + ) from err + try: + await session.handle.insert( + CATEGORY_MASTER_SECRET, + IndyCredxHolder.MASTER_SECRET_ID, + secret.to_json_buffer(), + ) + except AskarError as err: + if err.code != AskarErrorCode.DUPLICATE: + raise AnonCredsHolderError( + "Error saving master secret" + ) from err + # else: lost race to create record, retry + else: + break + return secret + + async def create_credential_request( + self, credential_offer: dict, credential_definition: dict, holder_did: str + ) -> Tuple[str, str]: + """ + Create a credential request for the given credential offer. + + Args: + credential_offer: The credential offer to create request for + credential_definition: The credential definition to create an offer for + holder_did: the DID of the agent making the request + + Returns: + A tuple of the credential request and credential request metadata + + """ + try: + secret = await self.get_master_secret() + ( + cred_req, + cred_req_metadata, + ) = await asyncio.get_event_loop().run_in_executor( + None, + CredentialRequest.create, + holder_did, + credential_definition, + secret, + IndyCredxHolder.MASTER_SECRET_ID, + credential_offer, + ) + except AnoncredsError as err: + raise AnonCredsHolderError("Error creating credential request") from err + cred_req_json, cred_req_metadata_json = ( + cred_req.to_json(), + cred_req_metadata.to_json(), + ) + + LOGGER.debug( + "Created credential request. " + "credential_request_json=%s credential_request_metadata_json=%s", + cred_req_json, + cred_req_metadata_json, + ) + + return cred_req_json, cred_req_metadata_json + + async def store_credential( + self, + credential_definition: dict, + credential_data: dict, + credential_request_metadata: dict, + credential_attr_mime_types: dict = None, + credential_id: str = None, + rev_reg_def: dict = None, + ) -> str: + """ + Store a credential in the wallet. + + Args: + credential_definition: Credential definition for this credential + credential_data: Credential data generated by the issuer + credential_request_metadata: credential request metadata generated + by the issuer + credential_attr_mime_types: dict mapping attribute names to (optional) + MIME types to store as non-secret record, if specified + credential_id: optionally override the stored credential id + rev_reg_def: revocation registry definition in json + + Returns: + the ID of the stored credential + + """ + try: + secret = await self.get_master_secret() + cred = Credential.load(credential_data) + cred_recvd = await asyncio.get_event_loop().run_in_executor( + None, + cred.process, + credential_request_metadata, + secret, + credential_definition, + rev_reg_def, + ) + except AnoncredsError as err: + raise AnonCredsHolderError("Error processing received credential") from err + + schema_id = cred_recvd.schema_id + schema_id_parts = re.match(r"^(\w+):2:([^:]+):([^:]+)$", schema_id) + if not schema_id_parts: + raise AnonCredsHolderError( + f"Error parsing credential schema ID: {schema_id}" + ) + cred_def_id = cred_recvd.cred_def_id + cdef_id_parts = re.match(r"^(\w+):3:CL:([^:]+):([^:]+)$", cred_def_id) + if not cdef_id_parts: + raise AnonCredsHolderError( + f"Error parsing credential definition ID: {cred_def_id}" + ) + + credential_id = credential_id or str(uuid.uuid4()) + tags = { + "schema_id": schema_id, + "schema_issuer_did": schema_id_parts[1], + "schema_name": schema_id_parts[2], + "schema_version": schema_id_parts[3], + "issuer_did": cdef_id_parts[1], + "cred_def_id": cred_def_id, + "rev_reg_id": cred_recvd.rev_reg_id or "None", + } + + # FIXME - sdk has some special handling for fully qualified DIDs here + + mime_types = {} + for k, attr_value in credential_data["values"].items(): + attr_name = _normalize_attr_name(k) + # tags[f"attr::{attr_name}::marker"] = "1" + tags[f"attr::{attr_name}::value"] = attr_value["raw"] + if credential_attr_mime_types and k in credential_attr_mime_types: + mime_types[k] = credential_attr_mime_types[k] + + try: + async with self._profile.transaction() as txn: + await txn.handle.insert( + CATEGORY_CREDENTIAL, + credential_id, + cred_recvd.to_json_buffer(), + tags=tags, + ) + if mime_types: + await txn.handle.insert( + AnonCredsHolder.RECORD_TYPE_MIME_TYPES, + credential_id, + value_json=mime_types, + ) + await txn.commit() + except AskarError as err: + raise AnonCredsHolderError("Error storing credential") from err + + return credential_id + + async def get_credentials(self, start: int, count: int, wql: dict): + """ + Get credentials stored in the wallet. + + Args: + start: Starting index + count: Number of records to return + wql: wql query dict + + """ + + result = [] + + try: + rows = self._profile.store.scan( + CATEGORY_CREDENTIAL, + wql, + start, + count, + self._profile.settings.get("wallet.askar_profile"), + ) + async for row in rows: + cred = Credential.load(row.raw_value) + result.append(_make_cred_info(row.name, cred)) + except AskarError as err: + raise AnonCredsHolderError("Error retrieving credentials") from err + except AnoncredsError as err: + raise AnonCredsHolderError("Error loading stored credential") from err + + return result + + async def get_credentials_for_presentation_request_by_referent( + self, + presentation_request: dict, + referents: Sequence[str], + start: int, + count: int, + extra_query: dict = {}, + ): + """ + Get credentials stored in the wallet. + + Args: + presentation_request: Valid presentation request from issuer + referents: Presentation request referents to use to search for creds + start: Starting index + count: Maximum number of records to return + extra_query: wql query dict + + """ + + if not referents: + referents = ( + *presentation_request["requested_attributes"], + *presentation_request["requested_predicates"], + ) + + creds = {} + + for reft in referents: + names = set() + if reft in presentation_request["requested_attributes"]: + attr = presentation_request["requested_attributes"][reft] + if "name" in attr: + names.add(_normalize_attr_name(attr["name"])) + elif "names" in attr: + names.update(_normalize_attr_name(name) for name in attr["names"]) + # for name in names: + # tag_filter[f"attr::{_normalize_attr_name(name)}::marker"] = "1" + restr = attr.get("restrictions") + elif reft in presentation_request["requested_predicates"]: + pred = presentation_request["requested_predicates"][reft] + if "name" in pred: + names.add(_normalize_attr_name(pred["name"])) + # tag_filter[f"attr::{_normalize_attr_name(name)}::marker"] = "1" + restr = pred.get("restrictions") + else: + raise AnonCredsHolderError( + f"Unknown presentation request referent: {reft}" + ) + + tag_filter = {"$exist": list(f"attr::{name}::value" for name in names)} + if restr: + # FIXME check if restr is a list or dict? validate WQL format + tag_filter = {"$and": [tag_filter] + restr} + if extra_query: + tag_filter = {"$and": [tag_filter, extra_query]} + + rows = self._profile.store.scan( + CATEGORY_CREDENTIAL, + tag_filter, + start, + count, + self._profile.settings.get("wallet.askar_profile"), + ) + async for row in rows: + if row.name in creds: + creds[row.name]["presentation_referents"].add(reft) + else: + cred_info = _make_cred_info( + row.name, Credential.load(row.raw_value) + ) + creds[row.name] = { + "cred_info": cred_info, + "interval": presentation_request.get("non_revoked"), + "presentation_referents": {reft}, + } + + for cred in creds.values(): + cred["presentation_referents"] = list(cred["presentation_referents"]) + + return list(creds.values()) + + async def get_credential(self, credential_id: str) -> str: + """ + Get a credential stored in the wallet. + + Args: + credential_id: Credential id to retrieve + + """ + cred = await self._get_credential(credential_id) + return json.dumps(_make_cred_info(credential_id, cred)) + + async def _get_credential(self, credential_id: str) -> Credential: + """Get an unencoded Credential instance from the store.""" + try: + async with self._profile.session() as session: + cred = await session.handle.fetch(CATEGORY_CREDENTIAL, credential_id) + except AskarError as err: + raise AnonCredsHolderError("Error retrieving credential") from err + + if not cred: + raise WalletNotFoundError( + f"Credential {credential_id} not found in wallet {self.profile.name}" + ) + + try: + return Credential.load(cred.raw_value) + except AnoncredsError as err: + raise AnonCredsHolderError("Error loading requested credential") from err + + async def credential_revoked( + self, ledger: BaseLedger, credential_id: str, fro: int = None, to: int = None + ) -> bool: + """ + Check ledger for revocation status of credential by cred id. + + Args: + credential_id: Credential id to check + + """ + cred = await self._get_credential(credential_id) + rev_reg_id = cred.rev_reg_id + + if rev_reg_id: + cred_rev_id = cred.rev_reg_index + (rev_reg_delta, _) = await ledger.get_revoc_reg_delta( + rev_reg_id, + fro, + to, + ) + return cred_rev_id in rev_reg_delta["value"].get("revoked", []) + else: + return False + + async def delete_credential(self, credential_id: str): + """ + Remove a credential stored in the wallet. + + Args: + credential_id: Credential id to remove + + """ + try: + async with self._profile.session() as session: + await session.handle.remove(CATEGORY_CREDENTIAL, credential_id) + await session.handle.remove( + AnonCredsHolder.RECORD_TYPE_MIME_TYPES, credential_id + ) + except AskarError as err: + if err.code == AskarErrorCode.NOT_FOUND: + pass + else: + raise AnonCredsHolderError("Error deleting credential") from err + + async def get_mime_type( + self, credential_id: str, attr: str = None + ) -> Union[dict, str]: + """ + Get MIME type per attribute (or for all attributes). + + Args: + credential_id: credential id + attr: attribute of interest or omit for all + + Returns: Attribute MIME type or dict mapping attribute names to MIME types + attr_meta_json = all_meta.tags.get(attr) + + """ + try: + async with self._profile.session() as session: + mime_types_record = await session.handle.fetch( + AnonCredsHolder.RECORD_TYPE_MIME_TYPES, + credential_id, + ) + except AskarError as err: + raise AnonCredsHolderError( + "Error retrieving credential mime types" + ) from err + values = mime_types_record and mime_types_record.value_json + if values: + return values.get(attr) if attr else values + + async def create_presentation( + self, + presentation_request: dict, + requested_credentials: dict, + schemas: dict, + credential_definitions: dict, + rev_states: dict = None, + ) -> str: + """ + Get credentials stored in the wallet. + + Args: + presentation_request: Valid indy format presentation request + requested_credentials: Indy format requested credentials + schemas: Indy formatted schemas JSON + credential_definitions: Indy formatted credential definitions JSON + rev_states: Indy format revocation states JSON + + """ + + creds: Dict[str, Credential] = {} + + def get_rev_state(cred_id: str, detail: dict): + cred = creds[cred_id] + rev_reg_id = cred.rev_reg_id + timestamp = detail.get("timestamp") if rev_reg_id else None + rev_state = None + if timestamp: + if not rev_states or rev_reg_id not in rev_states: + raise AnonCredsHolderError( + f"No revocation states provided for credential '{cred_id}' " + f"with rev_reg_id '{rev_reg_id}'" + ) + rev_state = rev_states[rev_reg_id].get(timestamp) + if not rev_state: + raise AnonCredsHolderError( + f"No revocation states provided for credential '{cred_id}' " + f"with rev_reg_id '{rev_reg_id}' at timestamp {timestamp}" + ) + return timestamp, rev_state + + self_attest = requested_credentials.get("self_attested_attributes") or {} + present_creds = PresentCredentials() + req_attrs = requested_credentials.get("requested_attributes") or {} + for reft, detail in req_attrs.items(): + cred_id = detail["cred_id"] + if cred_id not in creds: + # NOTE: could be optimized if multiple creds are requested + creds[cred_id] = await self._get_credential(cred_id) + timestamp, rev_state = get_rev_state(cred_id, detail) + present_creds.add_attributes( + creds[cred_id], + reft, + reveal=detail["revealed"], + timestamp=timestamp, + rev_state=rev_state, + ) + req_preds = requested_credentials.get("requested_predicates") or {} + for reft, detail in req_preds.items(): + cred_id = detail["cred_id"] + if cred_id not in creds: + # NOTE: could be optimized if multiple creds are requested + creds[cred_id] = await self._get_credential(cred_id) + timestamp, rev_state = get_rev_state(cred_id, detail) + present_creds.add_predicates( + creds[cred_id], + reft, + timestamp=timestamp, + rev_state=rev_state, + ) + + try: + secret = await self.get_master_secret() + presentation = await asyncio.get_event_loop().run_in_executor( + None, + Presentation.create, + presentation_request, + present_creds, + self_attest, + secret, + schemas.values(), + credential_definitions.values(), + ) + except AnoncredsError as err: + raise AnonCredsHolderError("Error creating presentation") from err + + return presentation.to_json() + + async def create_revocation_state( + self, + cred_rev_id: str, + rev_reg_def: dict, + rev_reg_delta: dict, + timestamp: int, + tails_file_path: str, + ) -> str: + """ + Create current revocation state for a received credential. + + Args: + cred_rev_id: credential revocation id in revocation registry + rev_reg_def: revocation registry definition + rev_reg_delta: revocation delta + timestamp: delta timestamp + + Returns: + the revocation state + + """ + + try: + rev_state = await asyncio.get_event_loop().run_in_executor( + None, + CredentialRevocationState.create, + rev_reg_def, + rev_reg_delta, + int(cred_rev_id), + timestamp, + tails_file_path, + ) + except AnoncredsError as err: + raise AnonCredsHolderError("Error creating revocation state") from err + return rev_state.to_json() diff --git a/aries_cloudagent/anoncreds/anoncreds/issuer.py b/aries_cloudagent/anoncreds/anoncreds/issuer.py new file mode 100644 index 0000000000..019516c6e5 --- /dev/null +++ b/aries_cloudagent/anoncreds/anoncreds/issuer.py @@ -0,0 +1,639 @@ +"""Indy issuer implementation.""" + +import asyncio +import logging + +from typing import Sequence, Tuple + +from aries_askar import AskarError + +from indy_credx import ( + Credential, + CredentialDefinition, + CredentialOffer, + CredentialRevocationConfig, + CredxError, + RevocationRegistry, + RevocationRegistryDefinition, + RevocationRegistryDelta, + Schema, +) + +from ...askar.profile import AskarProfile + +from ..issuer import ( + AnonCredsIssuer, + AnonCredsIssuerError, + AnonCredsIssuerRevocationRegistryFullError, + DEFAULT_CRED_DEF_TAG, + DEFAULT_SIGNATURE_TYPE, +) + +LOGGER = logging.getLogger(__name__) + +CATEGORY_CRED_DEF = "credential_def" +CATEGORY_CRED_DEF_PRIVATE = "credential_def_private" +CATEGORY_CRED_DEF_KEY_PROOF = "credential_def_key_proof" +CATEGORY_SCHEMA = "schema" +CATEGORY_REV_REG = "revocation_reg" +CATEGORY_REV_REG_INFO = "revocation_reg_info" +CATEGORY_REV_REG_DEF = "revocation_reg_def" +CATEGORY_REV_REG_DEF_PRIVATE = "revocation_reg_def_private" +CATEGORY_REV_REG_ISSUER = "revocation_reg_def_issuer" + + +class IndyCredxIssuer(AnonCredsIssuer): + """Indy-Credx issuer class.""" + + def __init__(self, profile: AskarProfile): + """ + Initialize an IndyCredxIssuer instance. + + Args: + profile: The active profile instance + + """ + self._profile = profile + + @property + def profile(self) -> AskarProfile: + """Accessor for the profile instance.""" + return self._profile + + async def create_schema( + self, + origin_did: str, + schema_name: str, + schema_version: str, + attribute_names: Sequence[str], + ) -> Tuple[str, str]: + """ + Create a new credential schema and store it in the wallet. + + Args: + origin_did: the DID issuing the credential definition + schema_name: the schema name + schema_version: the schema version + attribute_names: a sequence of schema attribute names + + Returns: + A tuple of the schema ID and JSON + + """ + try: + schema = Schema.create( + origin_did, schema_name, schema_version, attribute_names + ) + schema_id = schema.id + schema_json = schema.to_json() + async with self._profile.session() as session: + await session.handle.insert(CATEGORY_SCHEMA, schema_id, schema_json) + except CredxError as err: + raise AnonCredsIssuerError("Error creating schema") from err + except AskarError as err: + raise AnonCredsIssuerError("Error storing schema") from err + return (schema_id, schema_json) + + async def credential_definition_in_wallet( + self, credential_definition_id: str + ) -> bool: + """ + Check whether a given credential definition ID is present in the wallet. + + Args: + credential_definition_id: The credential definition ID to check + """ + try: + async with self._profile.session() as session: + return ( + await session.handle.fetch( + CATEGORY_CRED_DEF_PRIVATE, credential_definition_id + ) + ) is not None + except AskarError as err: + raise AnonCredsIssuerError( + "Error checking for credential definition" + ) from err + + async def create_and_store_credential_definition( + self, + origin_did: str, + schema: dict, + signature_type: str = None, + tag: str = None, + support_revocation: bool = False, + ) -> Tuple[str, str]: + """ + Create a new credential definition and store it in the wallet. + + Args: + origin_did: the DID issuing the credential definition + schema_json: the schema used as a basis + signature_type: the credential definition signature type (default 'CL') + tag: the credential definition tag + support_revocation: whether to enable revocation for this credential def + + Returns: + A tuple of the credential definition ID and JSON + + """ + try: + ( + cred_def, + cred_def_private, + key_proof, + ) = await asyncio.get_event_loop().run_in_executor( + None, + lambda: CredentialDefinition.create( + origin_did, + schema, + signature_type or DEFAULT_SIGNATURE_TYPE, + tag or DEFAULT_CRED_DEF_TAG, + support_revocation=support_revocation, + ), + ) + cred_def_id = cred_def.id + cred_def_json = cred_def.to_json() + except CredxError as err: + raise AnonCredsIssuerError("Error creating credential definition") from err + try: + async with self._profile.transaction() as txn: + await txn.handle.insert( + CATEGORY_CRED_DEF, + cred_def_id, + cred_def_json, + # Note: Indy-SDK uses a separate SchemaId record for this + tags={"schema_id": schema["id"]}, + ) + await txn.handle.insert( + CATEGORY_CRED_DEF_PRIVATE, + cred_def_id, + cred_def_private.to_json_buffer(), + ) + await txn.handle.insert( + CATEGORY_CRED_DEF_KEY_PROOF, cred_def_id, key_proof.to_json_buffer() + ) + await txn.commit() + except AskarError as err: + raise AnonCredsIssuerError("Error storing credential definition") from err + return (cred_def_id, cred_def_json) + + async def create_credential_offer(self, credential_definition_id: str) -> str: + """ + Create a credential offer for the given credential definition id. + + Args: + credential_definition_id: The credential definition to create an offer for + + Returns: + The new credential offer + + """ + try: + async with self._profile.session() as session: + cred_def = await session.handle.fetch( + CATEGORY_CRED_DEF, credential_definition_id + ) + key_proof = await session.handle.fetch( + CATEGORY_CRED_DEF_KEY_PROOF, credential_definition_id + ) + except AskarError as err: + raise AnonCredsIssuerError( + "Error retrieving credential definition" + ) from err + if not cred_def or not key_proof: + raise AnonCredsIssuerError( + "Credential definition not found for credential offer" + ) + try: + # The tag holds the full name of the schema, + # as opposed to just the sequence number + schema_id = cred_def.tags.get("schema_id") + cred_def = CredentialDefinition.load(cred_def.raw_value) + + credential_offer = CredentialOffer.create( + schema_id or cred_def.schema_id, + cred_def, + key_proof.raw_value, + ) + except CredxError as err: + raise AnonCredsIssuerError("Error creating credential offer") from err + + return credential_offer.to_json() + + async def create_credential( + self, + schema: dict, + credential_offer: dict, + credential_request: dict, + credential_values: dict, + revoc_reg_id: str = None, + tails_file_path: str = None, + ) -> Tuple[str, str]: + """ + Create a credential. + + Args + schema: Schema to create credential for + credential_offer: Credential Offer to create credential for + credential_request: Credential request to create credential for + credential_values: Values to go in credential + revoc_reg_id: ID of the revocation registry + tails_file_path: The location of the tails file + + Returns: + A tuple of created credential and revocation id + + """ + credential_definition_id = credential_offer["cred_def_id"] + try: + async with self._profile.session() as session: + cred_def = await session.handle.fetch( + CATEGORY_CRED_DEF, credential_definition_id + ) + cred_def_private = await session.handle.fetch( + CATEGORY_CRED_DEF_PRIVATE, credential_definition_id + ) + except AskarError as err: + raise AnonCredsIssuerError( + "Error retrieving credential definition" + ) from err + if not cred_def or not cred_def_private: + raise AnonCredsIssuerError( + "Credential definition not found for credential issuance" + ) + + raw_values = {} + schema_attributes = schema["attrNames"] + for attribute in schema_attributes: + # Ensure every attribute present in schema to be set. + # Extraneous attribute names are ignored. + try: + credential_value = credential_values[attribute] + except KeyError: + raise AnonCredsIssuerError( + "Provided credential values are missing a value " + f"for the schema attribute '{attribute}'" + ) + + raw_values[attribute] = str(credential_value) + + if revoc_reg_id: + try: + async with self._profile.transaction() as txn: + rev_reg = await txn.handle.fetch(CATEGORY_REV_REG, revoc_reg_id) + rev_reg_info = await txn.handle.fetch( + CATEGORY_REV_REG_INFO, revoc_reg_id, for_update=True + ) + rev_reg_def = await txn.handle.fetch( + CATEGORY_REV_REG_DEF, revoc_reg_id + ) + rev_key = await txn.handle.fetch( + CATEGORY_REV_REG_DEF_PRIVATE, revoc_reg_id + ) + if not rev_reg: + raise AnonCredsIssuerError("Revocation registry not found") + if not rev_reg_info: + raise AnonCredsIssuerError( + "Revocation registry metadata not found" + ) + if not rev_reg_def: + raise AnonCredsIssuerError( + "Revocation registry definition not found" + ) + if not rev_key: + raise AnonCredsIssuerError( + "Revocation registry definition private data not found" + ) + # NOTE: we increment the index ahead of time to keep the + # transaction short. The revocation registry itself will NOT + # be updated because we always use ISSUANCE_BY_DEFAULT. + # If something goes wrong later, the index will be skipped. + # FIXME - double check issuance type in case of upgraded wallet? + rev_info = rev_reg_info.value_json + rev_reg_index = rev_info["curr_id"] + 1 + try: + rev_reg_def = RevocationRegistryDefinition.load( + rev_reg_def.raw_value + ) + except CredxError as err: + raise AnonCredsIssuerError( + "Error loading revocation registry definition" + ) from err + if rev_reg_index > rev_reg_def.max_cred_num: + raise AnonCredsIssuerRevocationRegistryFullError( + "Revocation registry is full" + ) + rev_info["curr_id"] = rev_reg_index + await txn.handle.replace( + CATEGORY_REV_REG_INFO, revoc_reg_id, value_json=rev_info + ) + await txn.commit() + except AskarError as err: + raise AnonCredsIssuerError( + "Error updating revocation registry index" + ) from err + + revoc = CredentialRevocationConfig( + rev_reg_def, + rev_key.raw_value, + rev_reg.raw_value, + rev_reg_index, + rev_info.get("used_ids") or [], + tails_file_path, + ) + credential_revocation_id = str(rev_reg_index) + else: + revoc = None + credential_revocation_id = None + + try: + ( + credential, + _upd_rev_reg, + _delta, + ) = await asyncio.get_event_loop().run_in_executor( + None, + Credential.create, + cred_def.raw_value, + cred_def_private.raw_value, + credential_offer, + credential_request, + raw_values, + None, + revoc, + ) + except CredxError as err: + raise AnonCredsIssuerError("Error creating credential") from err + + return credential.to_json(), credential_revocation_id + + async def revoke_credentials( + self, + revoc_reg_id: str, + tails_file_path: str, + cred_revoc_ids: Sequence[str], + ) -> Tuple[str, Sequence[str]]: + """ + Revoke a set of credentials in a revocation registry. + + Args: + revoc_reg_id: ID of the revocation registry + tails_file_path: path to the local tails file + cred_revoc_ids: sequences of credential indexes in the revocation registry + + Returns: + Tuple with the combined revocation delta, list of cred rev ids not revoked + + """ + + delta = None + failed_crids = set() + max_attempt = 5 + attempt = 0 + + while True: + attempt += 1 + if attempt >= max_attempt: + raise AnonCredsIssuerError( + "Repeated conflict attempting to update registry" + ) + try: + async with self._profile.session() as session: + rev_reg_def = await session.handle.fetch( + CATEGORY_REV_REG_DEF, revoc_reg_id + ) + rev_reg = await session.handle.fetch(CATEGORY_REV_REG, revoc_reg_id) + rev_reg_info = await session.handle.fetch( + CATEGORY_REV_REG_INFO, revoc_reg_id + ) + if not rev_reg_def: + raise AnonCredsIssuerError( + "Revocation registry definition not found" + ) + if not rev_reg: + raise AnonCredsIssuerError("Revocation registry not found") + if not rev_reg_info: + raise AnonCredsIssuerError("Revocation registry metadata not found") + except AskarError as err: + raise AnonCredsIssuerError( + "Error retrieving revocation registry" + ) from err + + try: + rev_reg_def = RevocationRegistryDefinition.load(rev_reg_def.raw_value) + except CredxError as err: + raise AnonCredsIssuerError( + "Error loading revocation registry definition" + ) from err + + rev_crids = set() + failed_crids = set() + max_cred_num = rev_reg_def.max_cred_num + rev_info = rev_reg_info.value_json + used_ids = set(rev_info.get("used_ids") or []) + + for rev_id in cred_revoc_ids: + rev_id = int(rev_id) + if rev_id < 1 or rev_id > max_cred_num: + LOGGER.error( + "Skipping requested credential revocation" + "on rev reg id %s, cred rev id=%s not in range", + revoc_reg_id, + rev_id, + ) + failed_crids.add(rev_id) + elif rev_id > rev_info["curr_id"]: + LOGGER.warn( + "Skipping requested credential revocation" + "on rev reg id %s, cred rev id=%s not yet issued", + revoc_reg_id, + rev_id, + ) + failed_crids.add(rev_id) + elif rev_id in used_ids: + LOGGER.warn( + "Skipping requested credential revocation" + "on rev reg id %s, cred rev id=%s already revoked", + revoc_reg_id, + rev_id, + ) + failed_crids.add(rev_id) + else: + rev_crids.add(rev_id) + + if not rev_crids: + break + + try: + rev_reg = RevocationRegistry.load(rev_reg.raw_value) + except CredxError as err: + raise AnonCredsIssuerError("Error loading revocation registry") from err + + try: + delta = await asyncio.get_event_loop().run_in_executor( + None, + lambda: rev_reg.update( + rev_reg_def, + None, # issued + list(rev_crids), # revoked + tails_file_path, + ), + ) + except CredxError as err: + raise AnonCredsIssuerError( + "Error updating revocation registry" + ) from err + + try: + async with self._profile.transaction() as txn: + rev_reg_upd = await txn.handle.fetch( + CATEGORY_REV_REG, revoc_reg_id, for_update=True + ) + rev_info_upd = await txn.handle.fetch( + CATEGORY_REV_REG_INFO, revoc_reg_id, for_update=True + ) + if not rev_reg_upd or not rev_reg_info: + LOGGER.warn( + "Revocation registry missing, skipping update: {}", + revoc_reg_id, + ) + delta = None + break + rev_info_upd = rev_info_upd.value_json + if rev_info_upd != rev_info: + # handle concurrent update to the registry by retrying + continue + await txn.handle.replace( + CATEGORY_REV_REG, revoc_reg_id, rev_reg.to_json_buffer() + ) + used_ids.update(rev_crids) + rev_info_upd["used_ids"] = sorted(used_ids) + await txn.handle.replace( + CATEGORY_REV_REG_INFO, revoc_reg_id, value_json=rev_info_upd + ) + await txn.commit() + except AskarError as err: + raise AnonCredsIssuerError("Error saving revocation registry") from err + break + + return ( + delta and delta.to_json(), + [str(rev_id) for rev_id in sorted(failed_crids)], + ) + + async def merge_revocation_registry_deltas( + self, fro_delta: str, to_delta: str + ) -> str: + """ + Merge revocation registry deltas. + + Args: + fro_delta: original delta in JSON format + to_delta: incoming delta in JSON format + + Returns: + Merged delta in JSON format + + """ + + def update(d1, d2): + try: + delta = RevocationRegistryDelta.load(d1) + delta.update_with(d2) + return delta.to_json() + except CredxError as err: + raise AnonCredsIssuerError( + "Error merging revocation registry deltas" + ) from err + + return await asyncio.get_event_loop().run_in_executor( + None, update, fro_delta, to_delta + ) + + async def create_and_store_revocation_registry( + self, + origin_did: str, + cred_def_id: str, + revoc_def_type: str, + tag: str, + max_cred_num: int, + tails_base_path: str, + ) -> Tuple[str, str, str]: + """ + Create a new revocation registry and store it in the wallet. + + Args: + origin_did: the DID issuing the revocation registry + cred_def_id: the identifier of the related credential definition + revoc_def_type: the revocation registry type (default CL_ACCUM) + tag: the unique revocation registry tag + max_cred_num: the number of credentials supported in the registry + tails_base_path: where to store the tails file + issuance_type: optionally override the issuance type + + Returns: + A tuple of the revocation registry ID, JSON, and entry JSON + + """ + try: + async with self._profile.session() as session: + cred_def = await session.handle.fetch(CATEGORY_CRED_DEF, cred_def_id) + except AskarError as err: + raise AnonCredsIssuerError( + "Error retrieving credential definition" + ) from err + if not cred_def: + raise AnonCredsIssuerError( + "Credential definition not found for revocation registry" + ) + + try: + ( + rev_reg_def, + rev_reg_def_private, + rev_reg, + _rev_reg_delta, + ) = await asyncio.get_event_loop().run_in_executor( + None, + lambda: RevocationRegistryDefinition.create( + origin_did, + cred_def.raw_value, + tag, + revoc_def_type, + max_cred_num, + tails_dir_path=tails_base_path, + ), + ) + except CredxError as err: + raise AnonCredsIssuerError("Error creating revocation registry") from err + + rev_reg_def_id = rev_reg_def.id + rev_reg_def_json = rev_reg_def.to_json() + rev_reg_json = rev_reg.to_json() + + try: + async with self._profile.transaction() as txn: + await txn.handle.insert(CATEGORY_REV_REG, rev_reg_def_id, rev_reg_json) + await txn.handle.insert( + CATEGORY_REV_REG_INFO, + rev_reg_def_id, + value_json={"curr_id": 0, "used_ids": []}, + ) + await txn.handle.insert( + CATEGORY_REV_REG_DEF, rev_reg_def_id, rev_reg_def_json + ) + await txn.handle.insert( + CATEGORY_REV_REG_DEF_PRIVATE, + rev_reg_def_id, + rev_reg_def_private.to_json_buffer(), + ) + await txn.commit() + except AskarError as err: + raise AnonCredsIssuerError("Error saving new revocation registry") from err + + return ( + rev_reg_def_id, + rev_reg_def_json, + rev_reg_json, + ) diff --git a/aries_cloudagent/anoncreds/anoncreds/tests/__init__.py b/aries_cloudagent/anoncreds/anoncreds/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aries_cloudagent/anoncreds/anoncreds/tests/test_cred_issuance.py b/aries_cloudagent/anoncreds/anoncreds/tests/test_cred_issuance.py new file mode 100644 index 0000000000..027c16b94d --- /dev/null +++ b/aries_cloudagent/anoncreds/anoncreds/tests/test_cred_issuance.py @@ -0,0 +1,341 @@ +import json +import tempfile +import pytest + +from asynctest import mock as async_mock, TestCase as AsyncTestCase + +from ....askar.profile import AskarProfileManager +from ....config.injection_context import InjectionContext +from ....ledger.base import BaseLedger +from ....ledger.multiple_ledger.ledger_requests_executor import ( + IndyLedgerRequestsExecutor, +) + +from .. import issuer, holder, verifier + + +TEST_DID = "55GkHamhTU1ZbTbV2ab9DE" +SCHEMA_NAME = "resident" +SCHEMA_VERSION = "1.0" +SCHEMA_TXN = 1234 +SCHEMA_ID = f"{TEST_DID}:2:{SCHEMA_NAME}:{SCHEMA_VERSION}" +CRED_DEF_ID = f"{TEST_DID}:3:CL:{SCHEMA_TXN}:default" +REV_REG_ID = f"{TEST_DID}:4:{CRED_DEF_ID}:CL_ACCUM:0" +CRED_REFT = "attr_0_uuid" +PRES_REQ_NON_REV = { + "name": "pres-request", + "version": "1.0", + "nonce": "1234567890", + "requested_attributes": {CRED_REFT: {"names": ["name", "moniker"]}}, + "requested_predicates": {}, +} +TIMESTAMP = 99999 +PRES_REQ_REV = { + "name": "pres-request", + "version": "1.0", + "nonce": "1234567890", + "requested_attributes": {CRED_REFT: {"names": ["name", "moniker"]}}, + "requested_predicates": {}, + "non_revoked": {"to": TIMESTAMP}, +} + + +@pytest.mark.askar +@pytest.mark.indy_credx +class TestIndyCredxIssuance(AsyncTestCase): + async def setUp(self): + context = InjectionContext(enforce_typing=False) + mock_ledger = async_mock.MagicMock( + get_credential_definition=async_mock.CoroutineMock( + return_value={"value": {}} + ), + get_revoc_reg_delta=async_mock.CoroutineMock( + return_value=( + {"value": {"...": "..."}}, + 1234567890, + ) + ), + ) + mock_ledger.__aenter__ = async_mock.CoroutineMock(return_value=mock_ledger) + self.ledger = mock_ledger + + self.holder_profile = await AskarProfileManager().provision( + context, + { + "name": ":memory:", + "key": await AskarProfileManager.generate_store_key(), + "key_derivation_method": "RAW", + }, + ) + self.issuer_profile = await AskarProfileManager().provision( + context, + { + "name": ":memory:", + "key": await AskarProfileManager.generate_store_key(), + "key_derivation_method": "RAW", + }, + ) + self.issuer_profile._context.injector.bind_instance(BaseLedger, mock_ledger) + self.issuer_profile._context.injector.bind_instance( + IndyLedgerRequestsExecutor, + async_mock.MagicMock( + get_ledger_for_identifier=async_mock.CoroutineMock( + return_value=(None, mock_ledger) + ) + ), + ) + + self.holder = holder.IndyCredxHolder(self.holder_profile) + self.issuer = issuer.IndyCredxIssuer(self.issuer_profile) + self.verifier = verifier.IndyCredxVerifier(self.issuer_profile) + assert "IndyCredxHolder" in str(self.holder) + assert "IndyCredxIssuer" in str(self.issuer) + assert "IndyCredxVerifier" in str(self.verifier) + + async def test_issue_store_non_rev(self): + assert ( + self.issuer.make_schema_id(TEST_DID, SCHEMA_NAME, SCHEMA_VERSION) + == SCHEMA_ID + ) + + (s_id, schema_json) = await self.issuer.create_schema( + TEST_DID, + SCHEMA_NAME, + SCHEMA_VERSION, + ["name", "moniker"], + ) + assert s_id == SCHEMA_ID + schema = json.loads(schema_json) + schema["seqNo"] = SCHEMA_TXN + + assert ( + self.issuer.make_credential_definition_id(TEST_DID, schema, tag="default") + == CRED_DEF_ID + ) + + ( + cd_id, + cred_def_json, + ) = await self.issuer.create_and_store_credential_definition( + TEST_DID, schema, support_revocation=False + ) + assert cd_id == CRED_DEF_ID + assert await self.issuer.credential_definition_in_wallet(cd_id) + cred_def = json.loads(cred_def_json) + + cred_offer_json = await self.issuer.create_credential_offer(cd_id) + cred_offer = json.loads(cred_offer_json) + + cred_req_json, cred_req_meta_json = await self.holder.create_credential_request( + cred_offer, cred_def, TEST_DID + ) + cred_req = json.loads(cred_req_json) + cred_req_meta = json.loads(cred_req_meta_json) + + cred_json, cred_rev_id = await self.issuer.create_credential( + schema, + cred_offer, + cred_req, + {"name": "NAME", "moniker": "MONIKER"}, + revoc_reg_id=None, + tails_file_path=None, + ) + assert cred_rev_id is None + cred_data = json.loads(cred_json) + + cred_id = await self.holder.store_credential(cred_def, cred_data, cred_req_meta) + + found = await self.holder.get_credential(cred_id) + assert found + stored_cred = json.loads(found) + + assert not await self.holder.get_mime_type(cred_id, "name") + + creds = await self.holder.get_credentials(None, None, None) + assert len(creds) == 1 + assert creds[0] == stored_cred + + assert not await self.holder.credential_revoked(self.ledger, cred_id) + + pres_creds = ( + await self.holder.get_credentials_for_presentation_request_by_referent( + PRES_REQ_NON_REV, + None, + 0, + 10, + {}, + ) + ) + assert pres_creds == [ + { + "cred_info": stored_cred, + "interval": None, + "presentation_referents": [CRED_REFT], + } + ] + + pres_json = await self.holder.create_presentation( + PRES_REQ_NON_REV, + { + "requested_attributes": { + CRED_REFT: {"cred_id": cred_id, "revealed": True} + } + }, + {s_id: schema}, + {cd_id: cred_def}, + rev_states=None, + ) + pres = json.loads(pres_json) + + assert await self.verifier.verify_presentation( + PRES_REQ_NON_REV, pres, {s_id: schema}, {cd_id: cred_def}, {}, {} + ) + + await self.holder.delete_credential(cred_id) + + async def test_issue_store_rev(self): + assert ( + self.issuer.make_schema_id(TEST_DID, SCHEMA_NAME, SCHEMA_VERSION) + == SCHEMA_ID + ) + + (s_id, schema_json) = await self.issuer.create_schema( + TEST_DID, + SCHEMA_NAME, + SCHEMA_VERSION, + ["name", "moniker"], + ) + assert s_id == SCHEMA_ID + schema = json.loads(schema_json) + schema["seqNo"] = SCHEMA_TXN + + assert ( + self.issuer.make_credential_definition_id(TEST_DID, schema, tag="default") + == CRED_DEF_ID + ) + + ( + cd_id, + cred_def_json, + ) = await self.issuer.create_and_store_credential_definition( + TEST_DID, schema, support_revocation=True + ) + assert cd_id == CRED_DEF_ID + cred_def = json.loads(cred_def_json) + self.ledger.get_credential_definition.return_value = cred_def + + with tempfile.TemporaryDirectory() as tmp_path: + ( + reg_id, + reg_def_json, + reg_entry_json, + ) = await self.issuer.create_and_store_revocation_registry( + TEST_DID, cd_id, "CL_ACCUM", "0", 10, tmp_path + ) + assert reg_id == REV_REG_ID + reg_def = json.loads(reg_def_json) + reg_entry = json.loads(reg_entry_json) + tails_path = reg_def["value"]["tailsLocation"] + + cred_offer_json = await self.issuer.create_credential_offer(cd_id) + cred_offer = json.loads(cred_offer_json) + + ( + cred_req_json, + cred_req_meta_json, + ) = await self.holder.create_credential_request( + cred_offer, cred_def, TEST_DID + ) + cred_req = json.loads(cred_req_json) + cred_req_meta = json.loads(cred_req_meta_json) + + cred_json, cred_rev_id = await self.issuer.create_credential( + schema, + cred_offer, + cred_req, + {"name": "NAME", "moniker": "MONIKER"}, + revoc_reg_id=reg_id, + tails_file_path=tails_path, + ) + assert cred_rev_id == "1" + cred_data = json.loads(cred_json) + + cred_id = await self.holder.store_credential( + cred_def, + cred_data, + cred_req_meta, + rev_reg_def=reg_def, + ) + + found = await self.holder.get_credential(cred_id) + assert found + stored_cred = json.loads(found) + + creds = await self.holder.get_credentials(None, None, None) + assert len(creds) == 1 + assert creds[0] == stored_cred + + assert not await self.holder.credential_revoked(self.ledger, cred_id) + + pres_creds = ( + await self.holder.get_credentials_for_presentation_request_by_referent( + PRES_REQ_REV, + None, + 0, + 10, + {}, + ) + ) + assert pres_creds == [ + { + "cred_info": stored_cred, + "interval": {"to": TIMESTAMP}, + "presentation_referents": [CRED_REFT], + } + ] + + rev_state_time = 1 + rev_state_json = await self.holder.create_revocation_state( + cred_rev_id, reg_def, reg_entry, rev_state_time, tails_path + ) + rev_state_init = json.loads(rev_state_json) + rev_delta_init = {"ver": "1.0", "value": rev_state_init["rev_reg"]} + + (rev_delta_2_json, skipped_ids) = await self.issuer.revoke_credentials( + reg_id, tails_path, (1,) + ) + assert not skipped_ids + rev_delta_2 = json.loads(rev_delta_2_json) + + merged = await self.issuer.merge_revocation_registry_deltas( + rev_delta_init, rev_delta_2 + ) + + pres_json = await self.holder.create_presentation( + PRES_REQ_REV, + { + "requested_attributes": { + CRED_REFT: { + "cred_id": cred_id, + "revealed": True, + "timestamp": rev_state_time, + } + } + }, + {s_id: schema}, + {cd_id: cred_def}, + rev_states={reg_id: {rev_state_time: rev_state_init}}, + ) + pres = json.loads(pres_json) + + reg_def["txnTime"] = rev_state_time + assert await self.verifier.verify_presentation( + PRES_REQ_REV, + pres, + {s_id: schema}, + {cd_id: cred_def}, + {reg_id: reg_def}, + {reg_id: {rev_state_time: rev_delta_init}}, + ) + + await self.holder.delete_credential(cred_id) diff --git a/aries_cloudagent/anoncreds/anoncreds/verifier.py b/aries_cloudagent/anoncreds/anoncreds/verifier.py new file mode 100644 index 0000000000..941f4092dd --- /dev/null +++ b/aries_cloudagent/anoncreds/anoncreds/verifier.py @@ -0,0 +1,85 @@ +"""Indy-Credx verifier implementation.""" + +import asyncio +import logging + +from indy_credx import CredxError, Presentation + +from ...core.profile import Profile + +from ..verifier import AnonCredsVerifier, PresVerifyMsg + +LOGGER = logging.getLogger(__name__) + + +class IndyCredxVerifier(AnonCredsVerifier): + """Indy-Credx verifier class.""" + + def __init__(self, profile: Profile): + """ + Initialize an IndyCredxVerifier instance. + + Args: + profile: an active profile instance + + """ + self.profile = profile + + async def verify_presentation( + self, + pres_req, + pres, + schemas, + credential_definitions, + rev_reg_defs, + rev_reg_entries, + ) -> (bool, list): + """ + Verify a presentation. + + Args: + pres_req: Presentation request data + pres: Presentation data + schemas: Schema data + credential_definitions: credential definition data + rev_reg_defs: revocation registry definitions + rev_reg_entries: revocation registry entries + """ + + msgs = [] + try: + msgs += self.non_revoc_intervals(pres_req, pres, credential_definitions) + msgs += await self.check_timestamps( + self.profile, pres_req, pres, rev_reg_defs + ) + msgs += await self.pre_verify(pres_req, pres) + except ValueError as err: + s = str(err) + msgs.append(f"{PresVerifyMsg.PRES_VALUE_ERROR.value}::{s}") + LOGGER.error( + f"Presentation on nonce={pres_req['nonce']} " + f"cannot be validated: {str(err)}" + ) + return (False, msgs) + + try: + presentation = Presentation.load(pres) + verified = await asyncio.get_event_loop().run_in_executor( + None, + presentation.verify, + pres_req, + schemas.values(), + credential_definitions.values(), + rev_reg_defs.values(), + rev_reg_entries, + ) + except CredxError as err: + s = str(err) + msgs.append(f"{PresVerifyMsg.PRES_VERIFY_ERROR.value}::{s}") + LOGGER.exception( + f"Validation of presentation on nonce={pres_req['nonce']} " + "failed with error" + ) + verified = False + + return (verified, msgs) diff --git a/aries_cloudagent/anoncreds/credx/issuer.py b/aries_cloudagent/anoncreds/credx/issuer.py index 86ef68ee85..019516c6e5 100644 --- a/aries_cloudagent/anoncreds/credx/issuer.py +++ b/aries_cloudagent/anoncreds/credx/issuer.py @@ -24,7 +24,7 @@ from ..issuer import ( AnonCredsIssuer, AnonCredsIssuerError, - IndyIssuerRevocationRegistryFullError, + AnonCredsIssuerRevocationRegistryFullError, DEFAULT_CRED_DEF_TAG, DEFAULT_SIGNATURE_TYPE, ) @@ -321,7 +321,7 @@ async def create_credential( "Error loading revocation registry definition" ) from err if rev_reg_index > rev_reg_def.max_cred_num: - raise IndyIssuerRevocationRegistryFullError( + raise AnonCredsIssuerRevocationRegistryFullError( "Revocation registry is full" ) rev_info["curr_id"] = rev_reg_index From c24af8e6cef592790c2bac47db4b697bc61692cc Mon Sep 17 00:00:00 2001 From: Char Howland Date: Wed, 22 Feb 2023 14:49:41 -0700 Subject: [PATCH 005/150] fix: import anoncreds rather than indy_credx Signed-off-by: Char Howland --- .../anoncreds/anoncreds/issuer.py | 24 +++++++++---------- .../anoncreds/tests/test_cred_issuance.py | 2 +- .../anoncreds/anoncreds/verifier.py | 4 ++-- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/aries_cloudagent/anoncreds/anoncreds/issuer.py b/aries_cloudagent/anoncreds/anoncreds/issuer.py index 019516c6e5..ed979dd0b7 100644 --- a/aries_cloudagent/anoncreds/anoncreds/issuer.py +++ b/aries_cloudagent/anoncreds/anoncreds/issuer.py @@ -7,12 +7,12 @@ from aries_askar import AskarError -from indy_credx import ( +from anoncreds import ( Credential, CredentialDefinition, CredentialOffer, CredentialRevocationConfig, - CredxError, + AnoncredsError, RevocationRegistry, RevocationRegistryDefinition, RevocationRegistryDelta, @@ -88,7 +88,7 @@ async def create_schema( schema_json = schema.to_json() async with self._profile.session() as session: await session.handle.insert(CATEGORY_SCHEMA, schema_id, schema_json) - except CredxError as err: + except AnoncredsError as err: raise AnonCredsIssuerError("Error creating schema") from err except AskarError as err: raise AnonCredsIssuerError("Error storing schema") from err @@ -154,7 +154,7 @@ async def create_and_store_credential_definition( ) cred_def_id = cred_def.id cred_def_json = cred_def.to_json() - except CredxError as err: + except AnoncredsError as err: raise AnonCredsIssuerError("Error creating credential definition") from err try: async with self._profile.transaction() as txn: @@ -216,7 +216,7 @@ async def create_credential_offer(self, credential_definition_id: str) -> str: cred_def, key_proof.raw_value, ) - except CredxError as err: + except AnoncredsError as err: raise AnonCredsIssuerError("Error creating credential offer") from err return credential_offer.to_json() @@ -316,7 +316,7 @@ async def create_credential( rev_reg_def = RevocationRegistryDefinition.load( rev_reg_def.raw_value ) - except CredxError as err: + except AnoncredsError as err: raise AnonCredsIssuerError( "Error loading revocation registry definition" ) from err @@ -363,7 +363,7 @@ async def create_credential( None, revoc, ) - except CredxError as err: + except AnoncredsError as err: raise AnonCredsIssuerError("Error creating credential") from err return credential.to_json(), credential_revocation_id @@ -422,7 +422,7 @@ async def revoke_credentials( try: rev_reg_def = RevocationRegistryDefinition.load(rev_reg_def.raw_value) - except CredxError as err: + except AnoncredsError as err: raise AnonCredsIssuerError( "Error loading revocation registry definition" ) from err @@ -467,7 +467,7 @@ async def revoke_credentials( try: rev_reg = RevocationRegistry.load(rev_reg.raw_value) - except CredxError as err: + except AnoncredsError as err: raise AnonCredsIssuerError("Error loading revocation registry") from err try: @@ -480,7 +480,7 @@ async def revoke_credentials( tails_file_path, ), ) - except CredxError as err: + except AnoncredsError as err: raise AnonCredsIssuerError( "Error updating revocation registry" ) from err @@ -542,7 +542,7 @@ def update(d1, d2): delta = RevocationRegistryDelta.load(d1) delta.update_with(d2) return delta.to_json() - except CredxError as err: + except AnoncredsError as err: raise AnonCredsIssuerError( "Error merging revocation registry deltas" ) from err @@ -605,7 +605,7 @@ async def create_and_store_revocation_registry( tails_dir_path=tails_base_path, ), ) - except CredxError as err: + except AnonCredsError as err: raise AnonCredsIssuerError("Error creating revocation registry") from err rev_reg_def_id = rev_reg_def.id diff --git a/aries_cloudagent/anoncreds/anoncreds/tests/test_cred_issuance.py b/aries_cloudagent/anoncreds/anoncreds/tests/test_cred_issuance.py index 027c16b94d..fa2fded348 100644 --- a/aries_cloudagent/anoncreds/anoncreds/tests/test_cred_issuance.py +++ b/aries_cloudagent/anoncreds/anoncreds/tests/test_cred_issuance.py @@ -41,7 +41,7 @@ @pytest.mark.askar -@pytest.mark.indy_credx +@pytest.mark.anoncreds class TestIndyCredxIssuance(AsyncTestCase): async def setUp(self): context = InjectionContext(enforce_typing=False) diff --git a/aries_cloudagent/anoncreds/anoncreds/verifier.py b/aries_cloudagent/anoncreds/anoncreds/verifier.py index 941f4092dd..e0fc102e8b 100644 --- a/aries_cloudagent/anoncreds/anoncreds/verifier.py +++ b/aries_cloudagent/anoncreds/anoncreds/verifier.py @@ -3,7 +3,7 @@ import asyncio import logging -from indy_credx import CredxError, Presentation +from anoncreds import AnoncredsError, Presentation from ...core.profile import Profile @@ -73,7 +73,7 @@ async def verify_presentation( rev_reg_defs.values(), rev_reg_entries, ) - except CredxError as err: + except AnoncredsError as err: s = str(err) msgs.append(f"{PresVerifyMsg.PRES_VERIFY_ERROR.value}::{s}") LOGGER.exception( From 795cd8ff835f5923cd293fc3edb5770552300b35 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Wed, 22 Feb 2023 14:58:06 -0700 Subject: [PATCH 006/150] fix: update imports to use aries_cloudagent.anoncreds Signed-off-by: Char Howland --- aries_cloudagent/anoncreds/models/xform.py | 4 +- aries_cloudagent/anoncreds/sdk/holder.py | 4 +- aries_cloudagent/anoncreds/sdk/issuer.py | 8 +- aries_cloudagent/anoncreds/sdk/profile.py | 12 ++- .../anoncreds/sdk/tests/test_issuer.py | 4 +- aries_cloudagent/anoncreds/sdk/verifier.py | 2 +- aries_cloudagent/askar/profile.py | 10 +- aries_cloudagent/core/conductor.py | 4 +- aries_cloudagent/core/profile.py | 2 +- aries_cloudagent/holder/tests/test_routes.py | 22 ++--- .../tests/test_manager_provider.py | 4 +- aries_cloudagent/ledger/tests/test_indy.py | 52 +++++------ .../ledger/tests/test_indy_vdr.py | 20 ++-- .../credential_definitions/routes.py | 8 +- .../tests/test_routes.py | 6 +- .../decorators/tests/test_attach_decorator.py | 2 +- aries_cloudagent/messaging/schemas/routes.py | 8 +- .../messaging/schemas/tests/test_routes.py | 6 +- .../endorse_transaction/v1_0/manager.py | 6 +- .../endorse_transaction/v1_0/routes.py | 4 +- .../v1_0/handlers/credential_issue_handler.py | 4 +- .../v1_0/handlers/credential_offer_handler.py | 4 +- .../handlers/credential_proposal_handler.py | 4 +- .../handlers/credential_request_handler.py | 4 +- .../tests/test_credential_issue_handler.py | 2 +- .../tests/test_credential_offer_handler.py | 2 +- .../tests/test_credential_proposal_handler.py | 2 +- .../tests/test_credential_request_handler.py | 2 +- .../issue_credential/v1_0/manager.py | 91 +++++++++---------- .../v1_0/models/credential_exchange.py | 8 +- .../protocols/issue_credential/v1_0/routes.py | 14 +-- .../v1_0/tests/test_manager.py | 42 ++++----- .../v1_0/tests/test_routes.py | 2 +- .../v2_0/formats/indy/handler.py | 25 ++--- .../v2_0/formats/indy/tests/test_handler.py | 18 ++-- .../v2_0/handlers/cred_issue_handler.py | 4 +- .../v2_0/handlers/cred_offer_handler.py | 4 +- .../v2_0/handlers/cred_proposal_handler.py | 4 +- .../v2_0/handlers/cred_request_handler.py | 4 +- .../handlers/tests/test_cred_issue_handler.py | 2 +- .../handlers/tests/test_cred_offer_handler.py | 2 +- .../tests/test_cred_proposal_handler.py | 2 +- .../tests/test_cred_request_handler.py | 2 +- .../protocols/issue_credential/v2_0/routes.py | 16 ++-- .../v2_0/tests/test_manager.py | 4 +- .../v2_0/tests/test_routes.py | 2 +- .../present_proof/indy/pres_exch_handler.py | 8 +- .../handlers/presentation_request_handler.py | 8 +- .../test_presentation_request_handler.py | 22 ++--- .../protocols/present_proof/v1_0/manager.py | 4 +- .../v1_0/messages/presentation_proposal.py | 2 +- .../v1_0/messages/tests/test_presentation.py | 2 +- .../tests/test_presentation_proposal.py | 2 +- .../tests/test_presentation_request.py | 2 +- .../v1_0/models/presentation_exchange.py | 4 +- .../v1_0/models/tests/test_record.py | 2 +- .../protocols/present_proof/v1_0/routes.py | 18 ++-- .../present_proof/v1_0/tests/test_manager.py | 38 ++++---- .../present_proof/v1_0/tests/test_routes.py | 58 ++++++------ .../v2_0/formats/indy/handler.py | 18 ++-- .../v2_0/handlers/pres_request_handler.py | 4 +- .../tests/test_pres_request_handler.py | 18 ++-- .../v2_0/messages/tests/test_pres_format.py | 2 +- .../v2_0/messages/tests/test_pres_proposal.py | 2 +- .../v2_0/messages/tests/test_pres_request.py | 2 +- .../v2_0/models/tests/test_record.py | 2 +- .../protocols/present_proof/v2_0/routes.py | 16 ++-- .../present_proof/v2_0/tests/test_manager.py | 34 +++---- .../present_proof/v2_0/tests/test_routes.py | 58 ++++++------ .../models/issuer_rev_reg_record.py | 10 +- .../revocation/models/revocation_registry.py | 2 +- .../tests/test_issuer_rev_reg_record.py | 12 +-- .../models/tests/test_revocation_registry.py | 2 +- .../revocation/tests/test_manager.py | 30 +++--- .../storage/tests/test_indy_storage.py | 6 +- aries_cloudagent/storage/vc_holder/indy.py | 2 +- .../vc_holder/tests/test_indy_vc_holder.py | 2 +- .../wallet/tests/test_indy_wallet.py | 6 +- .../generated/aries_cloudagent.indy.credx.rst | 8 +- .../aries_cloudagent.indy.models.rst | 30 +++--- docs/generated/aries_cloudagent.indy.rst | 16 ++-- docs/generated/aries_cloudagent.indy.sdk.rst | 18 ++-- docs/generated/aries_cloudagent.rst | 2 +- 83 files changed, 464 insertions(+), 466 deletions(-) diff --git a/aries_cloudagent/anoncreds/models/xform.py b/aries_cloudagent/anoncreds/models/xform.py index afcb635fe2..8dd6ac0fd9 100644 --- a/aries_cloudagent/anoncreds/models/xform.py +++ b/aries_cloudagent/anoncreds/models/xform.py @@ -1,6 +1,6 @@ """Utilities to deal with indy.""" -from ...indy.holder import IndyHolder +from ...anoncreds.holder import AnonCredsHolder from .pres_preview import IndyPresPreview @@ -9,7 +9,7 @@ async def indy_proof_req_preview2indy_requested_creds( indy_proof_req: dict, preview: IndyPresPreview = None, *, - holder: IndyHolder, + holder: AnonCredsHolder, ): """ Build indy requested-credentials structure. diff --git a/aries_cloudagent/anoncreds/sdk/holder.py b/aries_cloudagent/anoncreds/sdk/holder.py index 42dab1dc34..7ad78b63a0 100644 --- a/aries_cloudagent/anoncreds/sdk/holder.py +++ b/aries_cloudagent/anoncreds/sdk/holder.py @@ -10,7 +10,7 @@ import indy.anoncreds from indy.error import ErrorCode, IndyError -from ...indy.sdk.wallet_setup import IndyOpenWallet +from ...anoncreds.sdk.wallet_setup import IndyOpenWallet from ...ledger.base import BaseLedger from ...storage.indy import IndySdkStorage from ...storage.error import StorageError, StorageNotFoundError @@ -30,7 +30,7 @@ class IndySdkHolder(AnonCredsHolder): def __init__(self, wallet: IndyOpenWallet): """ - Initialize an IndyHolder instance. + Initialize an AnonCredsHolder instance. Args: wallet: IndyOpenWallet instance diff --git a/aries_cloudagent/anoncreds/sdk/issuer.py b/aries_cloudagent/anoncreds/sdk/issuer.py index cd16fd67c9..74f54de218 100644 --- a/aries_cloudagent/anoncreds/sdk/issuer.py +++ b/aries_cloudagent/anoncreds/sdk/issuer.py @@ -8,14 +8,14 @@ import indy.blob_storage from indy.error import AnoncredsRevocationRegistryFullError, IndyError, ErrorCode -from ...indy.sdk.profile import IndySdkProfile +from ...anoncreds.sdk.profile import IndySdkProfile from ...messaging.util import encode from ...storage.error import StorageError from ..issuer import ( AnonCredsIssuer, AnonCredsIssuerError, - IndyIssuerRevocationRegistryFullError, + AnonCredsIssuerRevocationRegistryFullError, DEFAULT_CRED_DEF_TAG, DEFAULT_SIGNATURE_TYPE, ) @@ -31,7 +31,7 @@ class IndySdkIssuer(AnonCredsIssuer): def __init__(self, profile: IndySdkProfile): """ - Initialize an IndyIssuer instance. + Initialize an AnonCredsIssuer instance. Args: profile: IndySdkProfile instance @@ -221,7 +221,7 @@ async def create_credential( "Revocation registry %s is full: cannot create credential", rev_reg_id, ) - raise IndyIssuerRevocationRegistryFullError( + raise AnonCredsIssuerRevocationRegistryFullError( f"Revocation registry {rev_reg_id} is full" ) except IndyError as err: diff --git a/aries_cloudagent/anoncreds/sdk/profile.py b/aries_cloudagent/anoncreds/sdk/profile.py index b1bd27d663..109922da08 100644 --- a/aries_cloudagent/anoncreds/sdk/profile.py +++ b/aries_cloudagent/anoncreds/sdk/profile.py @@ -18,7 +18,7 @@ from ...wallet.indy import IndySdkWallet from ..holder import AnonCredsHolder -from ..issuer import IndyIssuer +from ..issuer import AnonCredsIssuer from ..verifier import AnonCredsVerifier from .wallet_setup import IndyWalletConfig, IndyOpenWallet @@ -75,12 +75,14 @@ def bind_providers(self): injector.bind_provider( AnonCredsHolder, ClassProvider( - "aries_cloudagent.indy.sdk.holder.IndySdkHolder", self.opened + "aries_cloudagent.anoncreds.sdk.holder.IndySdkHolder", self.opened ), ) injector.bind_provider( - IndyIssuer, - ClassProvider("aries_cloudagent.indy.sdk.issuer.IndySdkIssuer", ref(self)), + AnonCredsIssuer, + ClassProvider( + "aries_cloudagent.anoncreds.sdk.issuer.IndySdkIssuer", ref(self) + ), ) injector.bind_provider( @@ -98,7 +100,7 @@ def bind_providers(self): injector.bind_provider( AnonCredsVerifier, ClassProvider( - "aries_cloudagent.indy.sdk.verifier.IndySdkVerifier", + "aries_cloudagent.anoncreds.sdk.verifier.IndySdkVerifier", ref(self), ), ) diff --git a/aries_cloudagent/anoncreds/sdk/tests/test_issuer.py b/aries_cloudagent/anoncreds/sdk/tests/test_issuer.py index 4b847f29ae..d766fdbdd2 100644 --- a/aries_cloudagent/anoncreds/sdk/tests/test_issuer.py +++ b/aries_cloudagent/anoncreds/sdk/tests/test_issuer.py @@ -11,8 +11,8 @@ ) from ....config.injection_context import InjectionContext -from ....indy.sdk.profile import IndySdkProfile -from ....indy.sdk.wallet_setup import IndyWalletConfig +from ....anoncreds.sdk.profile import IndySdkProfile +from ....anoncreds.sdk.wallet_setup import IndyWalletConfig from ....wallet.indy import IndySdkWallet from ....ledger.indy import IndySdkLedgerPool diff --git a/aries_cloudagent/anoncreds/sdk/verifier.py b/aries_cloudagent/anoncreds/sdk/verifier.py index 09820fe649..7a3a39d389 100644 --- a/aries_cloudagent/anoncreds/sdk/verifier.py +++ b/aries_cloudagent/anoncreds/sdk/verifier.py @@ -18,7 +18,7 @@ class IndySdkVerifier(AnonCredsVerifier): def __init__(self, profile: Profile): """ - Initialize an IndyVerifier instance. + Initialize an AnonCredsVerifier instance. Args: profile: Active Profile instance diff --git a/aries_cloudagent/askar/profile.py b/aries_cloudagent/askar/profile.py index c6e8fa6361..e9a0a9ecaf 100644 --- a/aries_cloudagent/askar/profile.py +++ b/aries_cloudagent/askar/profile.py @@ -17,7 +17,7 @@ from ..core.error import ProfileError from ..core.profile import Profile, ProfileManager, ProfileSession from ..anoncreds.holder import AnonCredsHolder -from ..anoncreds.issuer import IndyIssuer +from ..anoncreds.issuer import AnonCredsIssuer from ..anoncreds.verifier import AnonCredsVerifier from ..ledger.base import BaseLedger from ..ledger.indy_vdr import IndyVdrLedger, IndyVdrLedgerPool @@ -103,14 +103,14 @@ def bind_providers(self): injector.bind_provider( AnonCredsHolder, ClassProvider( - "aries_cloudagent.indy.credx.holder.IndyCredxHolder", + "aries_cloudagent.anoncreds.credx.holder.IndyCredxHolder", ref(self), ), ) injector.bind_provider( - IndyIssuer, + AnonCredsIssuer, ClassProvider( - "aries_cloudagent.indy.credx.issuer.IndyCredxIssuer", ref(self) + "aries_cloudagent.anoncreds.credx.issuer.IndyCredxIssuer", ref(self) ), ) injector.bind_provider( @@ -129,7 +129,7 @@ def bind_providers(self): injector.bind_provider( AnonCredsVerifier, ClassProvider( - "aries_cloudagent.indy.credx.verifier.IndyCredxVerifier", + "aries_cloudagent.anoncreds.credx.verifier.IndyCredxVerifier", ref(self), ), ) diff --git a/aries_cloudagent/core/conductor.py b/aries_cloudagent/core/conductor.py index 022b131b25..6699f7a38e 100644 --- a/aries_cloudagent/core/conductor.py +++ b/aries_cloudagent/core/conductor.py @@ -147,7 +147,7 @@ async def setup(self): context.injector.bind_provider( AnonCredsVerifier, ClassProvider( - "aries_cloudagent.indy.credx.verifier.IndyCredxVerifier", + "aries_cloudagent.anoncreds.credx.verifier.IndyCredxVerifier", self.root_profile, ), ) @@ -158,7 +158,7 @@ async def setup(self): context.injector.bind_provider( AnonCredsVerifier, ClassProvider( - "aries_cloudagent.indy.sdk.verifier.IndySdkVerifier", + "aries_cloudagent.anoncreds.sdk.verifier.IndySdkVerifier", self.root_profile, ), ) diff --git a/aries_cloudagent/core/profile.py b/aries_cloudagent/core/profile.py index c78ed8d94f..25d4a9785b 100644 --- a/aries_cloudagent/core/profile.py +++ b/aries_cloudagent/core/profile.py @@ -306,7 +306,7 @@ class ProfileManagerProvider(BaseProvider): MANAGER_TYPES = { "askar": "aries_cloudagent.askar.profile.AskarProfileManager", "in_memory": "aries_cloudagent.core.in_memory.InMemoryProfileManager", - "indy": "aries_cloudagent.indy.sdk.profile.IndySdkProfileManager", + "indy": "aries_cloudagent.anoncreds.sdk.profile.IndySdkProfileManager", } def __init__(self): diff --git a/aries_cloudagent/holder/tests/test_routes.py b/aries_cloudagent/holder/tests/test_routes.py index e4edbc723a..4150a81636 100644 --- a/aries_cloudagent/holder/tests/test_routes.py +++ b/aries_cloudagent/holder/tests/test_routes.py @@ -8,7 +8,7 @@ from ...wallet.base import BaseWallet from ...admin.request_context import AdminRequestContext -from ...indy.holder import IndyHolder +from ...anoncreds.holder import AnonCredsHolder from ...ledger.base import BaseLedger from ...storage.vc_holder.base import VCHolder from ...storage.vc_holder.vc_record import VCRecord @@ -51,7 +51,7 @@ def setUp(self): async def test_credentials_get(self): self.request.match_info = {"credential_id": "dummy"} self.profile.context.injector.bind_instance( - IndyHolder, + AnonCredsHolder, async_mock.MagicMock( get_credential=async_mock.CoroutineMock( return_value=json.dumps({"hello": "world"}) @@ -69,7 +69,7 @@ async def test_credentials_get(self): async def test_credentials_get_not_found(self): self.request.match_info = {"credential_id": "dummy"} self.profile.context.injector.bind_instance( - IndyHolder, + AnonCredsHolder, async_mock.MagicMock( get_credential=async_mock.CoroutineMock( side_effect=test_module.WalletNotFoundError() @@ -86,7 +86,7 @@ async def test_credentials_revoked(self): BaseLedger, async_mock.create_autospec(BaseLedger) ) self.profile.context.injector.bind_instance( - IndyHolder, + AnonCredsHolder, async_mock.MagicMock( credential_revoked=async_mock.CoroutineMock(return_value=False) ), @@ -111,7 +111,7 @@ async def test_credentials_not_found(self): BaseLedger, async_mock.create_autospec(BaseLedger) ) self.profile.context.injector.bind_instance( - IndyHolder, + AnonCredsHolder, async_mock.MagicMock( credential_revoked=async_mock.CoroutineMock( side_effect=test_module.WalletNotFoundError("no such cred") @@ -129,7 +129,7 @@ async def test_credentials_x_ledger(self): BaseLedger, async_mock.create_autospec(BaseLedger) ) self.profile.context.injector.bind_instance( - IndyHolder, + AnonCredsHolder, async_mock.MagicMock( credential_revoked=async_mock.CoroutineMock( side_effect=test_module.LedgerError("down for maintenance") @@ -143,7 +143,7 @@ async def test_credentials_x_ledger(self): async def test_attribute_mime_types_get(self): self.request.match_info = {"credential_id": "dummy"} self.profile.context.injector.bind_instance( - IndyHolder, + AnonCredsHolder, async_mock.MagicMock( get_mime_type=async_mock.CoroutineMock( side_effect=[None, {"a": "application/jpeg"}] @@ -164,7 +164,7 @@ async def test_attribute_mime_types_get(self): async def test_credentials_remove(self): self.request.match_info = {"credential_id": "dummy"} self.profile.context.injector.bind_instance( - IndyHolder, + AnonCredsHolder, async_mock.MagicMock( delete_credential=async_mock.CoroutineMock(return_value=None) ), @@ -180,7 +180,7 @@ async def test_credentials_remove(self): async def test_credentials_remove_not_found(self): self.request.match_info = {"credential_id": "dummy"} self.profile.context.injector.bind_instance( - IndyHolder, + AnonCredsHolder, async_mock.MagicMock( delete_credential=async_mock.CoroutineMock( side_effect=test_module.WalletNotFoundError() @@ -193,7 +193,7 @@ async def test_credentials_remove_not_found(self): async def test_credentials_list(self): self.request.query = {"start": "0", "count": "10"} self.profile.context.injector.bind_instance( - IndyHolder, + AnonCredsHolder, async_mock.MagicMock( get_credentials=async_mock.CoroutineMock( return_value=[{"hello": "world"}] @@ -211,7 +211,7 @@ async def test_credentials_list(self): async def test_credentials_list_x_holder(self): self.request.query = {"start": "0", "count": "10"} self.profile.context.injector.bind_instance( - IndyHolder, + AnonCredsHolder, async_mock.MagicMock( get_credentials=async_mock.CoroutineMock( side_effect=test_module.AnonCredsHolderError() diff --git a/aries_cloudagent/ledger/multiple_ledger/tests/test_manager_provider.py b/aries_cloudagent/ledger/multiple_ledger/tests/test_manager_provider.py index 14c75c514b..d4c2854d38 100644 --- a/aries_cloudagent/ledger/multiple_ledger/tests/test_manager_provider.py +++ b/aries_cloudagent/ledger/multiple_ledger/tests/test_manager_provider.py @@ -5,8 +5,8 @@ from ....askar.profile import AskarProfileManager from ....config.injection_context import InjectionContext from ....core.in_memory import InMemoryProfile -from ....indy.sdk.profile import IndySdkProfile -from ....indy.sdk.wallet_setup import IndyOpenWallet, IndyWalletConfig +from ....anoncreds.sdk.profile import IndySdkProfile +from ....anoncreds.sdk.wallet_setup import IndyOpenWallet, IndyWalletConfig from ....ledger.base import BaseLedger from ....ledger.indy import IndySdkLedgerPool, IndySdkLedger diff --git a/aries_cloudagent/ledger/tests/test_indy.py b/aries_cloudagent/ledger/tests/test_indy.py index fe9c8216ee..b48c82bae3 100644 --- a/aries_cloudagent/ledger/tests/test_indy.py +++ b/aries_cloudagent/ledger/tests/test_indy.py @@ -9,8 +9,8 @@ from ...config.injection_context import InjectionContext from ...cache.in_memory import InMemoryCache -from ...indy.issuer import IndyIssuer, IndyIssuerError -from ...indy.sdk.profile import IndySdkProfile +from ...anoncreds.issuer import AnonCredsIssuer, AnonCredsIssuerError +from ...anoncreds.sdk.profile import IndySdkProfile from ...storage.record import StorageRecord from ...wallet.base import BaseWallet from ...wallet.did_info import DIDInfo @@ -548,7 +548,7 @@ async def test_send_schema( self.session.context.injector.bind_provider(BaseWallet, mock_wallet) mock_is_ledger_read_only.return_value = False - issuer = async_mock.MagicMock(IndyIssuer) + issuer = async_mock.MagicMock(AnonCredsIssuer) ledger = IndySdkLedger(IndySdkLedgerPool("name", checked=True), self.profile) issuer.create_schema.return_value = ("schema_issuer_did:name:1.0", "{}") mock_fetch_schema_by_id.return_value = None @@ -633,7 +633,7 @@ async def test_send_schema_already_exists( mock_wallet = async_mock.MagicMock() self.session.context.injector.bind_provider(BaseWallet, mock_wallet) - issuer = async_mock.MagicMock(IndyIssuer) + issuer = async_mock.MagicMock(AnonCredsIssuer) issuer.create_schema.return_value = ("1", "{}") ledger = IndySdkLedger(IndySdkLedgerPool("name", checked=True), self.profile) mock_add_record = async_mock.CoroutineMock() @@ -690,7 +690,7 @@ async def test_send_schema_ledger_transaction_error_already_exists( self.session.context.injector.bind_provider(BaseWallet, mock_wallet) mock_is_ledger_read_only.return_value = False - issuer = async_mock.MagicMock(IndyIssuer) + issuer = async_mock.MagicMock(AnonCredsIssuer) issuer.create_schema.return_value = ("1", "{}") ledger = IndySdkLedger(IndySdkLedgerPool("name", checked=True), self.profile) ledger._submit = async_mock.CoroutineMock( @@ -734,7 +734,7 @@ async def test_send_schema_ledger_read_only( mock_wallet = async_mock.MagicMock() self.session.context.injector.bind_provider(BaseWallet, mock_wallet) - issuer = async_mock.MagicMock(IndyIssuer) + issuer = async_mock.MagicMock(AnonCredsIssuer) issuer.create_schema.return_value = ("1", "{}") ledger = IndySdkLedger( IndySdkLedgerPool("name", checked=True, read_only=True), self.profile @@ -776,9 +776,9 @@ async def test_send_schema_issuer_error( self.session.context.injector.bind_provider(BaseWallet, mock_wallet) mock_is_ledger_read_only.return_value = False - issuer = async_mock.MagicMock(IndyIssuer) + issuer = async_mock.MagicMock(AnonCredsIssuer) issuer.create_schema = async_mock.CoroutineMock( - side_effect=IndyIssuerError("dummy error") + side_effect=AnonCredsIssuerError("dummy error") ) ledger = IndySdkLedger(IndySdkLedgerPool("name", checked=True), self.profile) with async_mock.patch.object( @@ -819,7 +819,7 @@ async def test_send_schema_ledger_transaction_error( mock_wallet = async_mock.MagicMock() self.session.context.injector.bind_provider(BaseWallet, mock_wallet) - issuer = async_mock.MagicMock(IndyIssuer) + issuer = async_mock.MagicMock(AnonCredsIssuer) issuer.create_schema.return_value = ("1", "{}") ledger = IndySdkLedger(IndySdkLedgerPool("name", checked=True), self.profile) ledger._submit = async_mock.CoroutineMock( @@ -863,7 +863,7 @@ async def test_send_schema_no_seq_no( ): mock_wallet = async_mock.MagicMock() self.session.context.injector.bind_provider(BaseWallet, mock_wallet) - issuer = async_mock.MagicMock(IndyIssuer) + issuer = async_mock.MagicMock(AnonCredsIssuer) mock_is_ledger_read_only.return_value = False ledger = IndySdkLedger(IndySdkLedgerPool("name", checked=True), self.profile) issuer.create_schema.return_value = ("schema_issuer_did:name:1.0", "{}") @@ -1180,7 +1180,7 @@ async def test_send_credential_definition( mock_fetch_cred_def.side_effect = [None, cred_def] - issuer = async_mock.MagicMock(IndyIssuer) + issuer = async_mock.MagicMock(AnonCredsIssuer) issuer.make_credential_definition_id.return_value = cred_def_id issuer.create_and_store_credential_definition.return_value = ( cred_def_id, @@ -1272,7 +1272,7 @@ async def test_send_credential_definition_endorse_only( mock_fetch_cred_def.side_effect = [None, cred_def] - issuer = async_mock.MagicMock(IndyIssuer) + issuer = async_mock.MagicMock(AnonCredsIssuer) issuer.make_credential_definition_id.return_value = cred_def_id issuer.create_and_store_credential_definition.return_value = ( cred_def_id, @@ -1350,7 +1350,7 @@ async def test_send_credential_definition_exists_in_ledger_and_wallet( mock_fetch_cred_def.return_value = {"mock": "cred-def"} - issuer = async_mock.MagicMock(IndyIssuer) + issuer = async_mock.MagicMock(AnonCredsIssuer) issuer.make_credential_definition_id.return_value = cred_def_id issuer.create_and_store_credential_definition.return_value = ( cred_def_id, @@ -1408,7 +1408,7 @@ async def test_send_credential_definition_no_such_schema( self.session.context.injector.bind_provider(BaseWallet, mock_wallet) mock_get_schema.return_value = {} - issuer = async_mock.MagicMock(IndyIssuer) + issuer = async_mock.MagicMock(AnonCredsIssuer) ledger = IndySdkLedger(IndySdkLedgerPool("name", checked=True), self.profile) schema_id = "schema_issuer_did:name:1.0" tag = "default" @@ -1449,8 +1449,8 @@ async def test_send_credential_definition_offer_exception( mock_get_schema.return_value = {"seqNo": 999} - issuer = async_mock.MagicMock(IndyIssuer) - issuer.credential_definition_in_wallet.side_effect = IndyIssuerError( + issuer = async_mock.MagicMock(AnonCredsIssuer) + issuer.credential_definition_in_wallet.side_effect = AnonCredsIssuerError( "common IO error" ) ledger = IndySdkLedger(IndySdkLedgerPool("name", checked=True), self.profile) @@ -1498,7 +1498,7 @@ async def test_send_credential_definition_cred_def_in_wallet_not_ledger( mock_fetch_cred_def.return_value = {} - issuer = async_mock.MagicMock(IndyIssuer) + issuer = async_mock.MagicMock(AnonCredsIssuer) ledger = IndySdkLedger(IndySdkLedgerPool("name", checked=True), self.profile) schema_id = "schema_issuer_did:name:1.0" tag = "default" @@ -1544,9 +1544,9 @@ async def test_send_credential_definition_cred_def_not_on_ledger_wallet_check_x( mock_fetch_cred_def.return_value = {} - issuer = async_mock.MagicMock(IndyIssuer) + issuer = async_mock.MagicMock(AnonCredsIssuer) issuer.credential_definition_in_wallet = async_mock.CoroutineMock( - side_effect=IndyIssuerError("dummy error") + side_effect=AnonCredsIssuerError("dummy error") ) ledger = IndySdkLedger(IndySdkLedgerPool("name", checked=True), self.profile) schema_id = "schema_issuer_did:name:1.0" @@ -1594,12 +1594,12 @@ async def test_send_credential_definition_cred_def_not_on_ledger_nor_wallet_send mock_fetch_cred_def.return_value = {} - issuer = async_mock.MagicMock(IndyIssuer) + issuer = async_mock.MagicMock(AnonCredsIssuer) issuer.credential_definition_in_wallet = async_mock.CoroutineMock( return_value=False ) issuer.create_and_store_credential_definition = async_mock.CoroutineMock( - side_effect=IndyIssuerError("dummy error") + side_effect=AnonCredsIssuerError("dummy error") ) ledger = IndySdkLedger(IndySdkLedgerPool("name", checked=True), self.profile) schema_id = "schema_issuer_did:name:1.0" @@ -1647,7 +1647,7 @@ async def test_send_credential_definition_read_only( mock_fetch_cred_def.return_value = {} - issuer = async_mock.MagicMock(IndyIssuer) + issuer = async_mock.MagicMock(AnonCredsIssuer) issuer.credential_definition_in_wallet = async_mock.CoroutineMock( return_value=False ) @@ -1702,7 +1702,7 @@ async def test_send_credential_definition_cred_def_on_ledger_not_in_wallet( mock_fetch_cred_def.return_value = cred_def - issuer = async_mock.MagicMock(IndyIssuer) + issuer = async_mock.MagicMock(AnonCredsIssuer) issuer.credential_definition_in_wallet = async_mock.CoroutineMock( return_value=False ) @@ -1761,7 +1761,7 @@ async def test_send_credential_definition_on_ledger_in_wallet( mock_fetch_cred_def.return_value = cred_def - issuer = async_mock.MagicMock(IndyIssuer) + issuer = async_mock.MagicMock(AnonCredsIssuer) issuer.make_credential_definition_id.return_value = cred_def_id issuer.create_and_store_credential_definition.return_value = ( cred_def_id, @@ -1844,8 +1844,8 @@ async def test_send_credential_definition_create_cred_def_exception( mock_fetch_cred_def.return_value = None - issuer = async_mock.MagicMock(IndyIssuer) - issuer.create_and_store_credential_definition.side_effect = IndyIssuerError( + issuer = async_mock.MagicMock(AnonCredsIssuer) + issuer.create_and_store_credential_definition.side_effect = AnonCredsIssuerError( "invalid structure" ) ledger = IndySdkLedger(IndySdkLedgerPool("name", checked=True), self.profile) diff --git a/aries_cloudagent/ledger/tests/test_indy_vdr.py b/aries_cloudagent/ledger/tests/test_indy_vdr.py index 6a575a5737..484ffbe297 100644 --- a/aries_cloudagent/ledger/tests/test_indy_vdr.py +++ b/aries_cloudagent/ledger/tests/test_indy_vdr.py @@ -7,7 +7,7 @@ import indy_vdr from ...core.in_memory import InMemoryProfile -from ...indy.issuer import IndyIssuer +from ...anoncreds.issuer import AnonCredsIssuer from ...wallet.base import BaseWallet from ...wallet.key_type import KeyType, ED25519 from ...wallet.did_method import SOV, DIDMethods @@ -202,7 +202,7 @@ async def test_send_schema( ): wallet = (await ledger.profile.session()).wallet test_did = await wallet.create_public_did(SOV, ED25519) - issuer = async_mock.MagicMock(IndyIssuer) + issuer = async_mock.MagicMock(AnonCredsIssuer) issuer.create_schema.return_value = ( "schema_issuer_did:schema_name:9.1", r'{"ver": "1.0", "id": "schema_issuer_did:schema_name:9.1", "name": "schema_name", "version": "9.1", "attrNames": ["a", "b"]}', @@ -249,7 +249,7 @@ async def test_send_schema_no_public_did( self, ledger: IndyVdrLedger, ): - issuer = async_mock.MagicMock(IndyIssuer) + issuer = async_mock.MagicMock(AnonCredsIssuer) async with ledger: with pytest.raises(BadLedgerRequestError): schema_id, schema_def = await ledger.create_and_send_schema( @@ -263,7 +263,7 @@ async def test_send_schema_already_exists( ): wallet = (await ledger.profile.session()).wallet test_did = await wallet.create_public_did(SOV, ED25519) - issuer = async_mock.MagicMock(IndyIssuer) + issuer = async_mock.MagicMock(AnonCredsIssuer) issuer.create_schema.return_value = ( "schema_issuer_did:schema_name:9.1", r'{"ver": "1.0", "id": "schema_issuer_did:schema_name:9.1", "name": "schema_name", "version": "9.1", "attrNames": ["a", "b"]}', @@ -293,7 +293,7 @@ async def test_send_schema_ledger_read_only( ): wallet = (await ledger.profile.session()).wallet test_did = await wallet.create_public_did(SOV, ED25519) - issuer = async_mock.MagicMock(IndyIssuer) + issuer = async_mock.MagicMock(AnonCredsIssuer) issuer.create_schema.return_value = ( "schema_issuer_did:schema_name:9.1", r'{"ver": "1.0", "id": "schema_issuer_did:schema_name:9.1", "name": "schema_name", "version": "9.1", "attrNames": ["a", "b"]}', @@ -322,7 +322,7 @@ async def test_send_schema_ledger_transaction_error( ): wallet = (await ledger.profile.session()).wallet test_did = await wallet.create_public_did(SOV, ED25519) - issuer = async_mock.MagicMock(IndyIssuer) + issuer = async_mock.MagicMock(AnonCredsIssuer) issuer.create_schema.return_value = ( "schema_issuer_did:schema_name:9.1", r'{"ver": "1.0", "id": "schema_issuer_did:schema_name:9.1", "name": "schema_name", "version": "9.1", "attrNames": ["a", "b"]}', @@ -401,7 +401,7 @@ async def test_send_credential_definition( } }, } - issuer = async_mock.MagicMock(IndyIssuer) + issuer = async_mock.MagicMock(AnonCredsIssuer) issuer.make_credential_definition_id.return_value = cred_def_id issuer.credential_definition_in_wallet.return_value = False issuer.create_and_store_credential_definition.return_value = ( @@ -438,7 +438,7 @@ async def test_send_credential_definition_no_public_did( self, ledger: IndyVdrLedger, ): - issuer = async_mock.MagicMock(IndyIssuer) + issuer = async_mock.MagicMock(AnonCredsIssuer) async with ledger: with pytest.raises(BadLedgerRequestError): await ledger.create_and_send_credential_definition( @@ -449,7 +449,7 @@ async def test_send_credential_definition_no_public_did( async def test_send_credential_definition_no_such_schema( self, ledger: IndyVdrLedger ): - issuer = async_mock.MagicMock(IndyIssuer) + issuer = async_mock.MagicMock(AnonCredsIssuer) async with ledger: ledger.pool_handle.submit_request.return_value = {} with pytest.raises(BadLedgerRequestError): @@ -459,7 +459,7 @@ async def test_send_credential_definition_no_such_schema( @pytest.mark.asyncio async def test_send_credential_definition_read_only(self, ledger: IndyVdrLedger): - issuer = async_mock.MagicMock(IndyIssuer) + issuer = async_mock.MagicMock(AnonCredsIssuer) async with ledger: ledger.pool.read_only = True with pytest.raises(LedgerError): diff --git a/aries_cloudagent/messaging/credential_definitions/routes.py b/aries_cloudagent/messaging/credential_definitions/routes.py index 5724b8a979..c75f976a2f 100644 --- a/aries_cloudagent/messaging/credential_definitions/routes.py +++ b/aries_cloudagent/messaging/credential_definitions/routes.py @@ -20,8 +20,8 @@ from ...admin.request_context import AdminRequestContext from ...core.event_bus import Event, EventBus from ...core.profile import Profile -from ...indy.issuer import IndyIssuer, IndyIssuerError -from ...indy.models.cred_def import CredentialDefinitionSchema +from ...anoncreds.issuer import AnonCredsIssuer, AnonCredsIssuerError +from ...anoncreds.models.cred_def import CredentialDefinitionSchema from ...ledger.base import BaseLedger from ...ledger.error import LedgerError from ...ledger.multiple_ledger.ledger_requests_executor import ( @@ -248,7 +248,7 @@ async def credential_definitions_send_credential_definition(request: web.BaseReq reason += ": missing wallet-type?" raise web.HTTPForbidden(reason=reason) - issuer = context.inject(IndyIssuer) + issuer = context.inject(AnonCredsIssuer) try: # even if in wallet, send it and raise if erroneously so async with ledger: (cred_def_id, cred_def, novel) = await shield( @@ -263,7 +263,7 @@ async def credential_definitions_send_credential_definition(request: web.BaseReq ) ) - except (IndyIssuerError, LedgerError) as e: + except (AnonCredsIssuerError, LedgerError) as e: raise web.HTTPBadRequest(reason=e.message) from e issuer_did = cred_def_id.split(":")[0] diff --git a/aries_cloudagent/messaging/credential_definitions/tests/test_routes.py b/aries_cloudagent/messaging/credential_definitions/tests/test_routes.py index 3a0cccb1b2..75bfc15534 100644 --- a/aries_cloudagent/messaging/credential_definitions/tests/test_routes.py +++ b/aries_cloudagent/messaging/credential_definitions/tests/test_routes.py @@ -3,7 +3,7 @@ from ....admin.request_context import AdminRequestContext from ....core.in_memory import InMemoryProfile -from ....indy.issuer import IndyIssuer +from ....anoncreds.issuer import AnonCredsIssuer from ....ledger.base import BaseLedger from ....ledger.multiple_ledger.ledger_requests_executor import ( IndyLedgerRequestsExecutor, @@ -40,8 +40,8 @@ def setUp(self): ) self.profile_injector.bind_instance(BaseLedger, self.ledger) - self.issuer = async_mock.create_autospec(IndyIssuer) - self.profile_injector.bind_instance(IndyIssuer, self.issuer) + self.issuer = async_mock.create_autospec(AnonCredsIssuer) + self.profile_injector.bind_instance(AnonCredsIssuer, self.issuer) self.storage = async_mock.create_autospec(BaseStorage) self.storage.find_all_records = async_mock.CoroutineMock( diff --git a/aries_cloudagent/messaging/decorators/tests/test_attach_decorator.py b/aries_cloudagent/messaging/decorators/tests/test_attach_decorator.py index c0f1f26169..72c1edc8e3 100644 --- a/aries_cloudagent/messaging/decorators/tests/test_attach_decorator.py +++ b/aries_cloudagent/messaging/decorators/tests/test_attach_decorator.py @@ -6,7 +6,7 @@ import pytest -from ....indy.sdk.wallet_setup import IndyWalletConfig +from ....anoncreds.sdk.wallet_setup import IndyWalletConfig from ....messaging.models.base import BaseModelError from ....wallet.did_method import SOV from ....wallet.indy import IndySdkWallet diff --git a/aries_cloudagent/messaging/schemas/routes.py b/aries_cloudagent/messaging/schemas/routes.py index aa3f48b0fa..61c8e9855a 100644 --- a/aries_cloudagent/messaging/schemas/routes.py +++ b/aries_cloudagent/messaging/schemas/routes.py @@ -20,8 +20,8 @@ from ...admin.request_context import AdminRequestContext from ...core.event_bus import Event, EventBus from ...core.profile import Profile -from ...indy.issuer import IndyIssuer, IndyIssuerError -from ...indy.models.schema import SchemaSchema +from ...anoncreds.issuer import AnonCredsIssuer, AnonCredsIssuerError +from ...anoncreds.models.schema import SchemaSchema from ...ledger.base import BaseLedger from ...ledger.error import LedgerError from ...ledger.multiple_ledger.ledger_requests_executor import ( @@ -241,7 +241,7 @@ async def schemas_send_schema(request: web.BaseRequest): reason += ": missing wallet-type?" raise web.HTTPForbidden(reason=reason) - issuer = context.inject(IndyIssuer) + issuer = context.inject(AnonCredsIssuer) async with ledger: try: # if create_transaction_for_endorser, then the returned "schema_def" @@ -256,7 +256,7 @@ async def schemas_send_schema(request: web.BaseRequest): endorser_did=endorser_did, ) ) - except (IndyIssuerError, LedgerError) as err: + except (AnonCredsIssuerError, LedgerError) as err: raise web.HTTPBadRequest(reason=err.roll_up) from err meta_data = { diff --git a/aries_cloudagent/messaging/schemas/tests/test_routes.py b/aries_cloudagent/messaging/schemas/tests/test_routes.py index f5347a6b2b..568cc96c6a 100644 --- a/aries_cloudagent/messaging/schemas/tests/test_routes.py +++ b/aries_cloudagent/messaging/schemas/tests/test_routes.py @@ -3,7 +3,7 @@ from ....admin.request_context import AdminRequestContext from ....core.in_memory import InMemoryProfile -from ....indy.issuer import IndyIssuer +from ....anoncreds.issuer import AnonCredsIssuer from ....ledger.base import BaseLedger from ....ledger.multiple_ledger.ledger_requests_executor import ( IndyLedgerRequestsExecutor, @@ -34,8 +34,8 @@ def setUp(self): ) self.profile_injector.bind_instance(BaseLedger, self.ledger) - self.issuer = async_mock.create_autospec(IndyIssuer) - self.profile_injector.bind_instance(IndyIssuer, self.issuer) + self.issuer = async_mock.create_autospec(AnonCredsIssuer) + self.profile_injector.bind_instance(AnonCredsIssuer, self.issuer) self.storage = async_mock.create_autospec(BaseStorage) self.storage.find_all_records = async_mock.CoroutineMock( diff --git a/aries_cloudagent/protocols/endorse_transaction/v1_0/manager.py b/aries_cloudagent/protocols/endorse_transaction/v1_0/manager.py index c41508bd20..1b4cd172f8 100644 --- a/aries_cloudagent/protocols/endorse_transaction/v1_0/manager.py +++ b/aries_cloudagent/protocols/endorse_transaction/v1_0/manager.py @@ -9,7 +9,7 @@ from ....connections.models.conn_record import ConnRecord from ....core.error import BaseError from ....core.profile import Profile -from ....indy.issuer import IndyIssuerError +from ....anoncreds.issuer import AnonCredsIssuerError from ....ledger.base import BaseLedger from ....ledger.error import LedgerError from ....messaging.credential_definitions.util import notify_cred_def_event @@ -437,7 +437,7 @@ async def complete_transaction( ledger_transaction, sign=False, taa_accept=False ) ) - except (IndyIssuerError, LedgerError) as err: + except (AnonCredsIssuerError, LedgerError) as err: raise TransactionManagerError(err.roll_up) from err ledger_response = json.loads(ledger_response_json) @@ -821,7 +821,7 @@ async def endorsed_txn_post_processing( try: schema_seq_no = str(ledger_response["result"]["txn"]["data"]["ref"]) schema_response = await shield(ledger.get_schema(schema_seq_no)) - except (IndyIssuerError, LedgerError) as err: + except (AnonCredsIssuerError, LedgerError) as err: raise TransactionManagerError(err.roll_up) from err schema_id = schema_response["id"] diff --git a/aries_cloudagent/protocols/endorse_transaction/v1_0/routes.py b/aries_cloudagent/protocols/endorse_transaction/v1_0/routes.py index b2f14ae7f0..559cd025c2 100644 --- a/aries_cloudagent/protocols/endorse_transaction/v1_0/routes.py +++ b/aries_cloudagent/protocols/endorse_transaction/v1_0/routes.py @@ -18,7 +18,7 @@ from ....core.event_bus import Event, EventBus from ....core.profile import Profile from ....core.util import STARTUP_EVENT_PATTERN, SHUTDOWN_EVENT_PATTERN -from ....indy.issuer import IndyIssuerError +from ....anoncreds.issuer import AnonCredsIssuerError from ....ledger.error import LedgerError from ....messaging.models.base import BaseModelError from ....messaging.models.openapi import OpenAPISchema @@ -355,7 +355,7 @@ async def endorse_transaction_response(request: web.BaseRequest): state=TransactionRecord.STATE_TRANSACTION_ENDORSED, use_endorser_did=endorser_did, ) - except (IndyIssuerError, LedgerError) as err: + except (AnonCredsIssuerError, LedgerError) as err: raise web.HTTPBadRequest(reason=err.roll_up) from err except (StorageError, TransactionManagerError) as err: raise web.HTTPBadRequest(reason=err.roll_up) from err diff --git a/aries_cloudagent/protocols/issue_credential/v1_0/handlers/credential_issue_handler.py b/aries_cloudagent/protocols/issue_credential/v1_0/handlers/credential_issue_handler.py index 7fe0bd7bf6..2ec672c086 100644 --- a/aries_cloudagent/protocols/issue_credential/v1_0/handlers/credential_issue_handler.py +++ b/aries_cloudagent/protocols/issue_credential/v1_0/handlers/credential_issue_handler.py @@ -1,7 +1,7 @@ """Credential issue message handler.""" from .....core.oob_processor import OobMessageProcessor -from .....indy.holder import IndyHolderError +from .....anoncreds.holder import AnonCredsHolderError from .....messaging.base_handler import BaseHandler, HandlerException from .....messaging.models.base import BaseModelError from .....messaging.request_context import RequestContext @@ -73,7 +73,7 @@ async def handle(self, context: RequestContext, responder: BaseResponder): except ( BaseModelError, CredentialManagerError, - IndyHolderError, + AnonCredsHolderError, StorageError, ) as err: # treat failure to store as mangled on receipt hence protocol error diff --git a/aries_cloudagent/protocols/issue_credential/v1_0/handlers/credential_offer_handler.py b/aries_cloudagent/protocols/issue_credential/v1_0/handlers/credential_offer_handler.py index a9e114ee40..fe5d39b099 100644 --- a/aries_cloudagent/protocols/issue_credential/v1_0/handlers/credential_offer_handler.py +++ b/aries_cloudagent/protocols/issue_credential/v1_0/handlers/credential_offer_handler.py @@ -3,7 +3,7 @@ from .....wallet.util import default_did_from_verkey from .....core.oob_processor import OobMessageProcessor -from .....indy.holder import IndyHolderError +from .....anoncreds.holder import AnonCredsHolderError from .....ledger.error import LedgerError from .....messaging.base_handler import BaseHandler, HandlerException from .....messaging.models.base import BaseModelError @@ -95,7 +95,7 @@ async def handle(self, context: RequestContext, responder: BaseResponder): except ( BaseModelError, CredentialManagerError, - IndyHolderError, + AnonCredsHolderError, LedgerError, StorageError, ) as err: diff --git a/aries_cloudagent/protocols/issue_credential/v1_0/handlers/credential_proposal_handler.py b/aries_cloudagent/protocols/issue_credential/v1_0/handlers/credential_proposal_handler.py index b42338aa10..71401296ec 100644 --- a/aries_cloudagent/protocols/issue_credential/v1_0/handlers/credential_proposal_handler.py +++ b/aries_cloudagent/protocols/issue_credential/v1_0/handlers/credential_proposal_handler.py @@ -1,6 +1,6 @@ """Credential proposal message handler.""" -from .....indy.issuer import IndyIssuerError +from .....anoncreds.issuer import AnonCredsIssuerError from .....ledger.error import LedgerError from .....messaging.base_handler import BaseHandler, HandlerException from .....messaging.models.base import BaseModelError @@ -73,7 +73,7 @@ async def handle(self, context: RequestContext, responder: BaseResponder): except ( BaseModelError, CredentialManagerError, - IndyIssuerError, + AnonCredsIssuerError, LedgerError, StorageError, ) as err: diff --git a/aries_cloudagent/protocols/issue_credential/v1_0/handlers/credential_request_handler.py b/aries_cloudagent/protocols/issue_credential/v1_0/handlers/credential_request_handler.py index f9bc43e70e..ff9b363313 100644 --- a/aries_cloudagent/protocols/issue_credential/v1_0/handlers/credential_request_handler.py +++ b/aries_cloudagent/protocols/issue_credential/v1_0/handlers/credential_request_handler.py @@ -1,7 +1,7 @@ """Credential request message handler.""" from .....core.oob_processor import OobMessageProcessor -from .....indy.issuer import IndyIssuerError +from .....anoncreds.issuer import AnonCredsIssuerError from .....ledger.error import LedgerError from .....messaging.base_handler import BaseHandler, HandlerException from .....messaging.models.base import BaseModelError @@ -84,7 +84,7 @@ async def handle(self, context: RequestContext, responder: BaseResponder): except ( BaseModelError, CredentialManagerError, - IndyIssuerError, + AnonCredsIssuerError, LedgerError, StorageError, ) as err: diff --git a/aries_cloudagent/protocols/issue_credential/v1_0/handlers/tests/test_credential_issue_handler.py b/aries_cloudagent/protocols/issue_credential/v1_0/handlers/tests/test_credential_issue_handler.py index 0f831927a1..9363d78f38 100644 --- a/aries_cloudagent/protocols/issue_credential/v1_0/handlers/tests/test_credential_issue_handler.py +++ b/aries_cloudagent/protocols/issue_credential/v1_0/handlers/tests/test_credential_issue_handler.py @@ -107,7 +107,7 @@ async def test_called_auto_store_x(self): ) ), store_credential=async_mock.CoroutineMock( - side_effect=test_module.IndyHolderError() + side_effect=test_module.AnonCredsHolderError() ), send_credential_ack=async_mock.CoroutineMock( return_value=( diff --git a/aries_cloudagent/protocols/issue_credential/v1_0/handlers/tests/test_credential_offer_handler.py b/aries_cloudagent/protocols/issue_credential/v1_0/handlers/tests/test_credential_offer_handler.py index a9aedbb0a9..5fadf9f63a 100644 --- a/aries_cloudagent/protocols/issue_credential/v1_0/handlers/tests/test_credential_offer_handler.py +++ b/aries_cloudagent/protocols/issue_credential/v1_0/handlers/tests/test_credential_offer_handler.py @@ -106,7 +106,7 @@ async def test_called_auto_request_x(self): ) ) mock_cred_mgr.return_value.create_request = async_mock.CoroutineMock( - side_effect=test_module.IndyHolderError() + side_effect=test_module.AnonCredsHolderError() ) request_context.message = CredentialOffer() diff --git a/aries_cloudagent/protocols/issue_credential/v1_0/handlers/tests/test_credential_proposal_handler.py b/aries_cloudagent/protocols/issue_credential/v1_0/handlers/tests/test_credential_proposal_handler.py index 911f056d43..b38bcb3e88 100644 --- a/aries_cloudagent/protocols/issue_credential/v1_0/handlers/tests/test_credential_proposal_handler.py +++ b/aries_cloudagent/protocols/issue_credential/v1_0/handlers/tests/test_credential_proposal_handler.py @@ -80,7 +80,7 @@ async def test_called_auto_offer_x(self): ) mock_cred_mgr.return_value.receive_proposal.return_value.auto_offer = True mock_cred_mgr.return_value.create_offer = async_mock.CoroutineMock( - side_effect=test_module.IndyIssuerError() + side_effect=test_module.AnonCredsIssuerError() ) request_context.message = CredentialProposal() diff --git a/aries_cloudagent/protocols/issue_credential/v1_0/handlers/tests/test_credential_request_handler.py b/aries_cloudagent/protocols/issue_credential/v1_0/handlers/tests/test_credential_request_handler.py index b74bf445ef..a89bc0dd56 100644 --- a/aries_cloudagent/protocols/issue_credential/v1_0/handlers/tests/test_credential_request_handler.py +++ b/aries_cloudagent/protocols/issue_credential/v1_0/handlers/tests/test_credential_request_handler.py @@ -138,7 +138,7 @@ async def test_called_auto_issue_x(self): ) mock_cred_mgr.return_value.receive_request.return_value.auto_issue = True mock_cred_mgr.return_value.issue_credential = async_mock.CoroutineMock( - side_effect=test_module.IndyIssuerError() + side_effect=test_module.AnonCredsIssuerError() ) request_context.message = CredentialRequest() diff --git a/aries_cloudagent/protocols/issue_credential/v1_0/manager.py b/aries_cloudagent/protocols/issue_credential/v1_0/manager.py index 1bca37dea8..88463924b9 100644 --- a/aries_cloudagent/protocols/issue_credential/v1_0/manager.py +++ b/aries_cloudagent/protocols/issue_credential/v1_0/manager.py @@ -10,8 +10,11 @@ from ....connections.models.conn_record import ConnRecord from ....core.error import BaseError from ....core.profile import Profile -from ....indy.holder import IndyHolder, IndyHolderError -from ....indy.issuer import IndyIssuer, IndyIssuerRevocationRegistryFullError +from ....anoncreds.holder import AnonCredsHolder, AnonCredsHolderError +from ....anoncreds.issuer import ( + AnonCredsIssuer, + AnonCredsIssuerRevocationRegistryFullError, +) from ....ledger.multiple_ledger.ledger_requests_executor import ( GET_CRED_DEF, GET_SCHEMA, @@ -245,7 +248,7 @@ async def create_offer( """ async def _create(cred_def_id): - issuer = self._profile.inject(IndyIssuer) + issuer = self._profile.inject(AnonCredsIssuer) offer_json = await issuer.create_credential_offer(cred_def_id) return json.loads(offer_json) @@ -356,15 +359,13 @@ async def receive_offer( # Get credential exchange record (holder sent proposal first) # or create it (issuer sent offer first) try: - cred_ex_record = ( - await ( - V10CredentialExchange.retrieve_by_connection_and_thread( - txn, - connection_id, - message._thread_id, - role=V10CredentialExchange.ROLE_HOLDER, - for_update=True, - ) + cred_ex_record = await ( + V10CredentialExchange.retrieve_by_connection_and_thread( + txn, + connection_id, + message._thread_id, + role=V10CredentialExchange.ROLE_HOLDER, + for_update=True, ) ) except StorageNotFoundError: # issuer sent this offer free of any proposal @@ -435,7 +436,7 @@ async def _create(): credential_definition_id ) - holder = self._profile.inject(IndyHolder) + holder = self._profile.inject(AnonCredsHolder) request_json, metadata_json = await holder.create_credential_request( cred_offer_ser, credential_definition, @@ -534,15 +535,13 @@ async def receive_request( async with self._profile.transaction() as txn: try: - cred_ex_record = ( - await ( - V10CredentialExchange.retrieve_by_connection_and_thread( - txn, - connection_id, - message._thread_id, - role=V10CredentialExchange.ROLE_ISSUER, - for_update=True, - ) + cred_ex_record = await ( + V10CredentialExchange.retrieve_by_connection_and_thread( + txn, + connection_id, + message._thread_id, + role=V10CredentialExchange.ROLE_ISSUER, + for_update=True, ) ) except StorageNotFoundError: @@ -614,7 +613,7 @@ async def issue_credential( schema_id = cred_ex_record.schema_id cred_def_id = cred_ex_record.credential_definition_id - issuer = self.profile.inject(IndyIssuer) + issuer = self.profile.inject(AnonCredsIssuer) multitenant_mgr = self.profile.inject_or(BaseMultitenantManager) if multitenant_mgr: ledger_exec_inst = IndyLedgerRequestsExecutor(self.profile) @@ -666,7 +665,7 @@ async def issue_credential( rev_reg_id, tails_path, ) - except IndyIssuerRevocationRegistryFullError: + except AnonCredsIssuerRevocationRegistryFullError: # unlucky, another instance filled the registry first continue @@ -745,15 +744,13 @@ async def receive_credential( async with self._profile.transaction() as txn: try: - cred_ex_record = ( - await ( - V10CredentialExchange.retrieve_by_connection_and_thread( - txn, - connection_id, - message._thread_id, - role=V10CredentialExchange.ROLE_HOLDER, - for_update=True, - ) + cred_ex_record = await ( + V10CredentialExchange.retrieve_by_connection_and_thread( + txn, + connection_id, + message._thread_id, + role=V10CredentialExchange.ROLE_HOLDER, + for_update=True, ) ) except StorageNotFoundError: @@ -818,7 +815,7 @@ async def store_credential( raw_cred_serde.de.rev_reg_id ) - holder = self._profile.inject(IndyHolder) + holder = self._profile.inject(AnonCredsHolder) if ( cred_ex_record.credential_proposal_dict and cred_ex_record.credential_proposal_dict.credential_proposal @@ -841,7 +838,7 @@ async def store_credential( credential_id=credential_id, rev_reg_def=revoc_reg_def, ) - except IndyHolderError as e: + except AnonCredsHolderError as e: LOGGER.error("Error storing credential: %s: %s", e.error_code, e.message) raise e @@ -953,15 +950,13 @@ async def receive_credential_ack( """ async with self._profile.transaction() as txn: try: - cred_ex_record = ( - await ( - V10CredentialExchange.retrieve_by_connection_and_thread( - txn, - connection_id, - message._thread_id, - role=V10CredentialExchange.ROLE_ISSUER, - for_update=True, - ) + cred_ex_record = await ( + V10CredentialExchange.retrieve_by_connection_and_thread( + txn, + connection_id, + message._thread_id, + role=V10CredentialExchange.ROLE_ISSUER, + for_update=True, ) ) except StorageNotFoundError: @@ -995,11 +990,9 @@ async def receive_problem_report( """ async with self._profile.transaction() as txn: try: - cred_ex_record = ( - await ( - V10CredentialExchange.retrieve_by_connection_and_thread( - txn, connection_id, message._thread_id, for_update=True - ) + cred_ex_record = await ( + V10CredentialExchange.retrieve_by_connection_and_thread( + txn, connection_id, message._thread_id, for_update=True ) ) except StorageNotFoundError: diff --git a/aries_cloudagent/protocols/issue_credential/v1_0/models/credential_exchange.py b/aries_cloudagent/protocols/issue_credential/v1_0/models/credential_exchange.py index 0daeb81e47..13e4e9ddd6 100644 --- a/aries_cloudagent/protocols/issue_credential/v1_0/models/credential_exchange.py +++ b/aries_cloudagent/protocols/issue_credential/v1_0/models/credential_exchange.py @@ -7,10 +7,10 @@ from marshmallow import fields, validate from .....core.profile import ProfileSession -from .....indy.models.cred import IndyCredential, IndyCredentialSchema -from .....indy.models.cred_abstract import IndyCredAbstract, IndyCredAbstractSchema -from .....indy.models.cred_precis import IndyCredInfo, IndyCredInfoSchema -from .....indy.models.cred_request import IndyCredRequest, IndyCredRequestSchema +from .....anoncreds.models.cred import IndyCredential, IndyCredentialSchema +from .....anoncreds.models.cred_abstract import IndyCredAbstract, IndyCredAbstractSchema +from .....anoncreds.models.cred_precis import IndyCredInfo, IndyCredInfoSchema +from .....anoncreds.models.cred_request import IndyCredRequest, IndyCredRequestSchema from .....messaging.models.base_record import BaseExchangeRecord, BaseExchangeSchema from .....messaging.valid import INDY_CRED_DEF_ID, INDY_SCHEMA_ID, UUIDFour from .....storage.base import StorageError diff --git a/aries_cloudagent/protocols/issue_credential/v1_0/routes.py b/aries_cloudagent/protocols/issue_credential/v1_0/routes.py index 3cba79cb88..0d385bdf82 100644 --- a/aries_cloudagent/protocols/issue_credential/v1_0/routes.py +++ b/aries_cloudagent/protocols/issue_credential/v1_0/routes.py @@ -16,8 +16,8 @@ from ....admin.request_context import AdminRequestContext from ....connections.models.conn_record import ConnRecord from ....core.profile import Profile -from ....indy.holder import IndyHolderError -from ....indy.issuer import IndyIssuerError +from ....anoncreds.holder import AnonCredsHolderError +from ....anoncreds.issuer import AnonCredsIssuerError from ....ledger.error import LedgerError from ....messaging.credential_definitions.util import CRED_DEF_TAGS from ....messaging.models.base import BaseModelError @@ -741,7 +741,7 @@ async def credential_exchange_create_free_offer(request: web.BaseRequest): except ( BaseModelError, CredentialManagerError, - IndyIssuerError, + AnonCredsIssuerError, LedgerError, StorageError, ) as err: @@ -923,7 +923,7 @@ async def credential_exchange_send_bound_offer(request: web.BaseRequest): except ( BaseModelError, CredentialManagerError, - IndyIssuerError, + AnonCredsIssuerError, LedgerError, StorageError, ) as err: @@ -1025,7 +1025,7 @@ async def credential_exchange_send_request(request: web.BaseRequest): except ( BaseModelError, CredentialManagerError, - IndyHolderError, + AnonCredsHolderError, LedgerError, StorageError, ) as err: @@ -1121,7 +1121,7 @@ async def credential_exchange_issue(request: web.BaseRequest): except ( BaseModelError, CredentialManagerError, - IndyIssuerError, + AnonCredsIssuerError, LedgerError, StorageError, ) as err: @@ -1216,7 +1216,7 @@ async def credential_exchange_store(request: web.BaseRequest): except ( CredentialManagerError, - IndyHolderError, + AnonCredsHolderError, StorageError, ) as err: # treat failure to store as mangled on receipt hence protocol error if cred_ex_record: diff --git a/aries_cloudagent/protocols/issue_credential/v1_0/tests/test_manager.py b/aries_cloudagent/protocols/issue_credential/v1_0/tests/test_manager.py index 838f5abdf4..c9ca5565c3 100644 --- a/aries_cloudagent/protocols/issue_credential/v1_0/tests/test_manager.py +++ b/aries_cloudagent/protocols/issue_credential/v1_0/tests/test_manager.py @@ -10,8 +10,8 @@ from .....core.in_memory import InMemoryProfile from .....cache.base import BaseCache from .....cache.in_memory import InMemoryCache -from .....indy.holder import IndyHolder -from .....indy.issuer import IndyIssuer +from .....anoncreds.holder import AnonCredsHolder +from .....anoncreds.issuer import AnonCredsIssuer from .....messaging.decorators.thread_decorator import ThreadDecorator from .....messaging.credential_definitions.util import CRED_DEF_SENT_RECORD_TYPE from .....messaging.responder import BaseResponder, MockResponder @@ -300,11 +300,11 @@ async def test_create_free_offer(self): self.cache = InMemoryCache() self.context.injector.bind_instance(BaseCache, self.cache) - issuer = async_mock.MagicMock(IndyIssuer, autospec=True) + issuer = async_mock.MagicMock(AnonCredsIssuer, autospec=True) issuer.create_credential_offer = async_mock.CoroutineMock( return_value=json.dumps(INDY_OFFER) ) - self.context.injector.bind_instance(IndyIssuer, issuer) + self.context.injector.bind_instance(AnonCredsIssuer, issuer) cred_def_record = StorageRecord( CRED_DEF_SENT_RECORD_TYPE, @@ -382,11 +382,11 @@ async def test_create_free_offer_attr_mismatch(self): self.cache = InMemoryCache() self.context.injector.bind_instance(BaseCache, self.cache) - issuer = async_mock.MagicMock(IndyIssuer, autospec=True) + issuer = async_mock.MagicMock(AnonCredsIssuer, autospec=True) issuer.create_credential_offer = async_mock.CoroutineMock( return_value=json.dumps(INDY_OFFER) ) - self.context.injector.bind_instance(IndyIssuer, issuer) + self.context.injector.bind_instance(AnonCredsIssuer, issuer) cred_def_record = StorageRecord( CRED_DEF_SENT_RECORD_TYPE, @@ -440,11 +440,11 @@ async def test_create_bound_offer(self): V10CredentialExchange, "set_cached_key", autospec=True ) as set_cached_key: get_cached_key.return_value = None - issuer = async_mock.MagicMock(IndyIssuer, autospec=True) + issuer = async_mock.MagicMock(AnonCredsIssuer, autospec=True) issuer.create_credential_offer = async_mock.CoroutineMock( return_value=json.dumps(INDY_OFFER) ) - self.context.injector.bind_instance(IndyIssuer, issuer) + self.context.injector.bind_instance(AnonCredsIssuer, issuer) cred_def_record = StorageRecord( CRED_DEF_SENT_RECORD_TYPE, @@ -515,7 +515,7 @@ async def test_create_bound_offer_no_cred_def(self): issuer.create_credential_offer = async_mock.CoroutineMock( return_value=INDY_OFFER ) - self.context.injector.bind_instance(IndyIssuer, issuer) + self.context.injector.bind_instance(AnonCredsIssuer, issuer) with self.assertRaises(CredentialManagerError): await self.manager.create_offer( @@ -656,7 +656,7 @@ async def test_create_request(self): holder.create_credential_request = async_mock.CoroutineMock( return_value=(json.dumps(INDY_CRED_REQ), json.dumps(cred_req_meta)) ) - self.context.injector.bind_instance(IndyHolder, holder) + self.context.injector.bind_instance(AnonCredsHolder, holder) ret_exchange, ret_request = await self.manager.create_request( stored_exchange, holder_did @@ -727,7 +727,7 @@ async def test_create_request_no_cache(self): holder.create_credential_request = async_mock.CoroutineMock( return_value=(json.dumps(INDY_CRED_REQ), json.dumps(cred_req_meta)) ) - self.context.injector.bind_instance(IndyHolder, holder) + self.context.injector.bind_instance(AnonCredsHolder, holder) ret_exchange, ret_request = await self.manager.create_request( stored_exchange, holder_did @@ -914,7 +914,7 @@ async def test_issue_credential_revocable(self): issuer.create_credential = async_mock.CoroutineMock( return_value=(json.dumps(cred), cred_rev_id) ) - self.context.injector.bind_instance(IndyIssuer, issuer) + self.context.injector.bind_instance(AnonCredsIssuer, issuer) with async_mock.patch.object( test_module, "IndyRevocation", autospec=True @@ -1001,7 +1001,7 @@ async def test_issue_credential_non_revocable(self): issuer.create_credential = async_mock.CoroutineMock( return_value=(json.dumps(cred), None) ) - self.context.injector.bind_instance(IndyIssuer, issuer) + self.context.injector.bind_instance(AnonCredsIssuer, issuer) Ledger = async_mock.MagicMock() self.ledger = Ledger() @@ -1072,7 +1072,7 @@ async def test_issue_credential_fills_rr(self): issuer.create_credential = async_mock.CoroutineMock( return_value=(json.dumps(cred), stored_exchange.revocation_id) ) - self.context.injector.bind_instance(IndyIssuer, issuer) + self.context.injector.bind_instance(AnonCredsIssuer, issuer) with async_mock.patch.object( test_module, "IndyRevocation", autospec=True @@ -1176,7 +1176,7 @@ async def test_issue_credential_no_active_rr_no_retries(self): issuer.create_credential = async_mock.CoroutineMock( return_value=(json.dumps(cred), cred_rev_id) ) - self.context.injector.bind_instance(IndyIssuer, issuer) + self.context.injector.bind_instance(AnonCredsIssuer, issuer) self.context.injector.bind_instance( IndyLedgerRequestsExecutor, async_mock.MagicMock( @@ -1242,7 +1242,7 @@ async def test_issue_credential_no_active_rr_retry(self): issuer.create_credential = async_mock.CoroutineMock( return_value=(json.dumps(cred), cred_rev_id) ) - self.context.injector.bind_instance(IndyIssuer, issuer) + self.context.injector.bind_instance(AnonCredsIssuer, issuer) self.context.injector.bind_instance( IndyLedgerRequestsExecutor, async_mock.MagicMock( @@ -1341,7 +1341,7 @@ async def test_store_credential(self): holder.get_credential = async_mock.CoroutineMock( return_value=json.dumps(INDY_CRED_INFO) ) - self.context.injector.bind_instance(IndyHolder, holder) + self.context.injector.bind_instance(AnonCredsHolder, holder) self.context.injector.bind_instance( IndyLedgerRequestsExecutor, async_mock.MagicMock( @@ -1450,7 +1450,7 @@ async def test_store_credential_no_preview(self): holder.get_credential = async_mock.CoroutineMock( return_value=json.dumps(cred_info_no_rev) ) - self.context.injector.bind_instance(IndyHolder, holder) + self.context.injector.bind_instance(AnonCredsHolder, holder) self.context.injector.bind_instance( IndyLedgerRequestsExecutor, async_mock.MagicMock( @@ -1517,9 +1517,9 @@ async def test_store_credential_holder_store_indy_error(self): cred_id = "cred-id" holder = async_mock.MagicMock() holder.store_credential = async_mock.CoroutineMock( - side_effect=test_module.IndyHolderError("Problem", {"message": "Nope"}) + side_effect=test_module.AnonCredsHolderError("Problem", {"message": "Nope"}) ) - self.context.injector.bind_instance(IndyHolder, holder) + self.context.injector.bind_instance(AnonCredsHolder, holder) self.context.injector.bind_instance( IndyLedgerRequestsExecutor, async_mock.MagicMock( @@ -1528,7 +1528,7 @@ async def test_store_credential_holder_store_indy_error(self): ) ), ) - with self.assertRaises(test_module.IndyHolderError): + with self.assertRaises(test_module.AnonCredsHolderError): await self.manager.store_credential( cred_ex_record=stored_exchange, credential_id=cred_id ) diff --git a/aries_cloudagent/protocols/issue_credential/v1_0/tests/test_routes.py b/aries_cloudagent/protocols/issue_credential/v1_0/tests/test_routes.py index 129f7eceb1..15084fd48a 100644 --- a/aries_cloudagent/protocols/issue_credential/v1_0/tests/test_routes.py +++ b/aries_cloudagent/protocols/issue_credential/v1_0/tests/test_routes.py @@ -1152,7 +1152,7 @@ async def test_credential_exchange_issue_rev_reg_full(self): mock_conn_rec.retrieve_by_id.return_value.is_ready = True mock_issue_cred = async_mock.CoroutineMock( - side_effect=test_module.IndyIssuerError() + side_effect=test_module.AnonCredsIssuerError() ) mock_credential_manager.return_value.issue_credential = mock_issue_cred diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/handler.py b/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/handler.py index 7ef901260f..31102eb443 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/handler.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/handler.py @@ -8,11 +8,14 @@ import asyncio from ......cache.base import BaseCache -from ......indy.issuer import IndyIssuer, IndyIssuerRevocationRegistryFullError -from ......indy.holder import IndyHolder, IndyHolderError -from ......indy.models.cred import IndyCredentialSchema -from ......indy.models.cred_request import IndyCredRequestSchema -from ......indy.models.cred_abstract import IndyCredAbstractSchema +from ......anoncreds.issuer import ( + AnonCredsIssuer, + AnonCredsIssuerRevocationRegistryFullError, +) +from ......anoncreds.holder import AnonCredsHolder, AnonCredsHolderError +from ......anoncreds.models.cred import IndyCredentialSchema +from ......anoncreds.models.cred_request import IndyCredRequestSchema +from ......anoncreds.models.cred_abstract import IndyCredAbstractSchema from ......ledger.base import BaseLedger from ......ledger.multiple_ledger.ledger_requests_executor import ( GET_CRED_DEF, @@ -188,7 +191,7 @@ async def create_offer( ) -> CredFormatAttachment: """Create indy credential offer.""" - issuer = self.profile.inject(IndyIssuer) + issuer = self.profile.inject(AnonCredsIssuer) ledger = self.profile.inject(BaseLedger) cache = self.profile.inject_or(BaseCache) @@ -279,7 +282,7 @@ async def _create(): async with ledger: cred_def = await ledger.get_credential_definition(cred_def_id) - holder = self.profile.inject(IndyHolder) + holder = self.profile.inject(AnonCredsHolder) request_json, metadata_json = await holder.create_credential_request( cred_offer, cred_def, holder_did ) @@ -337,7 +340,7 @@ async def issue_credential( schema_id = cred_offer["schema_id"] cred_def_id = cred_offer["cred_def_id"] - issuer = self.profile.inject(IndyIssuer) + issuer = self.profile.inject(AnonCredsIssuer) multitenant_mgr = self.profile.inject_or(BaseMultitenantManager) if multitenant_mgr: ledger_exec_inst = IndyLedgerRequestsExecutor(self.profile) @@ -385,7 +388,7 @@ async def issue_credential( rev_reg_id, tails_path, ) - except IndyIssuerRevocationRegistryFullError: + except AnonCredsIssuerRevocationRegistryFullError: # unlucky, another instance filled the registry first continue @@ -460,7 +463,7 @@ async def store_credential( if cred.get("rev_reg_id"): rev_reg_def = await ledger.get_revoc_reg_def(cred["rev_reg_id"]) - holder = self.profile.inject(IndyHolder) + holder = self.profile.inject(AnonCredsHolder) cred_offer_message = cred_ex_record.cred_offer mime_types = None if cred_offer_message and cred_offer_message.credential_preview: @@ -494,6 +497,6 @@ async def store_credential( await detail_record.save( session, reason="store credential v2.0", event=True ) - except IndyHolderError as e: + except AnonCredsHolderError as e: LOGGER.error(f"Error storing credential: {e.error_code} - {e.message}") raise e diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/tests/test_handler.py b/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/tests/test_handler.py index 06028fa00e..80a98d2c71 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/tests/test_handler.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/tests/test_handler.py @@ -16,14 +16,14 @@ ) from .......multitenant.base import BaseMultitenantManager from .......multitenant.manager import MultitenantManager -from .......indy.issuer import IndyIssuer +from .......anoncreds.issuer import AnonCredsIssuer from .......cache.in_memory import InMemoryCache from .......cache.base import BaseCache from .......storage.record import StorageRecord from .......storage.error import StorageNotFoundError from .......messaging.credential_definitions.util import CRED_DEF_SENT_RECORD_TYPE from .......messaging.decorators.attach_decorator import AttachDecorator -from .......indy.holder import IndyHolder +from .......anoncreds.holder import AnonCredsHolder from ....models.detail.indy import V20CredExRecordIndy from ....messages.cred_proposal import V20CredProposal from ....messages.cred_format import V20CredFormat @@ -233,12 +233,12 @@ async def setUp(self): self.context.injector.bind_instance(BaseCache, self.cache) # Issuer - self.issuer = async_mock.MagicMock(IndyIssuer, autospec=True) - self.context.injector.bind_instance(IndyIssuer, self.issuer) + self.issuer = async_mock.MagicMock(AnonCredsIssuer, autospec=True) + self.context.injector.bind_instance(AnonCredsIssuer, self.issuer) # Holder - self.holder = async_mock.MagicMock(IndyHolder, autospec=True) - self.context.injector.bind_instance(IndyHolder, self.holder) + self.holder = async_mock.MagicMock(AnonCredsHolder, autospec=True) + self.context.injector.bind_instance(AnonCredsHolder, self.holder) self.handler = IndyCredFormatHandler(self.profile) assert self.handler.profile @@ -1044,7 +1044,7 @@ async def test_issue_credential_rr_full(self): ) self.issuer.create_credential = async_mock.CoroutineMock( - side_effect=test_module.IndyIssuerRevocationRegistryFullError("Nope") + side_effect=test_module.AnonCredsIssuerRevocationRegistryFullError("Nope") ) with async_mock.patch.object( test_module, "IndyRevocation", autospec=True @@ -1263,7 +1263,7 @@ async def test_store_credential_holder_store_indy_error(self): cred_id = "cred-id" self.holder.store_credential = async_mock.CoroutineMock( - side_effect=test_module.IndyHolderError("Problem", {"message": "Nope"}) + side_effect=test_module.AnonCredsHolderError("Problem", {"message": "Nope"}) ) with async_mock.patch.object( @@ -1278,6 +1278,6 @@ async def test_store_credential_holder_store_indy_error(self): mock_rev_reg.return_value = async_mock.MagicMock( get_or_fetch_local_tails_path=async_mock.CoroutineMock() ) - with self.assertRaises(test_module.IndyHolderError) as context: + with self.assertRaises(test_module.AnonCredsHolderError) as context: await self.handler.store_credential(stored_cx_rec, cred_id) assert "Nope" in str(context.exception) diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/handlers/cred_issue_handler.py b/aries_cloudagent/protocols/issue_credential/v2_0/handlers/cred_issue_handler.py index ee148857f1..8e88fa4c9f 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/handlers/cred_issue_handler.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/handlers/cred_issue_handler.py @@ -1,7 +1,7 @@ """Credential issue message handler.""" from .....core.oob_processor import OobMessageProcessor -from .....indy.holder import IndyHolderError +from .....anoncreds.holder import AnonCredsHolderError from .....messaging.base_handler import BaseHandler, HandlerException from .....messaging.models.base import BaseModelError from .....messaging.request_context import RequestContext @@ -71,7 +71,7 @@ async def handle(self, context: RequestContext, responder: BaseResponder): cred_ex_record = await cred_manager.store_credential(cred_ex_record) except ( BaseModelError, - IndyHolderError, + AnonCredsHolderError, StorageError, V20CredManagerError, ) as err: diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/handlers/cred_offer_handler.py b/aries_cloudagent/protocols/issue_credential/v2_0/handlers/cred_offer_handler.py index 9fc223c4c0..1b1e0e532c 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/handlers/cred_offer_handler.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/handlers/cred_offer_handler.py @@ -2,7 +2,7 @@ from .....wallet.util import default_did_from_verkey from .....core.oob_processor import OobMessageProcessor -from .....indy.holder import IndyHolderError +from .....anoncreds.holder import AnonCredsHolderError from .....ledger.error import LedgerError from .....messaging.base_handler import BaseHandler, HandlerException from .....messaging.models.base import BaseModelError @@ -89,7 +89,7 @@ async def handle(self, context: RequestContext, responder: BaseResponder): await responder.send_reply(cred_request_message) except ( BaseModelError, - IndyHolderError, + AnonCredsHolderError, LedgerError, StorageError, V20CredManagerError, diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/handlers/cred_proposal_handler.py b/aries_cloudagent/protocols/issue_credential/v2_0/handlers/cred_proposal_handler.py index e59e9a36d2..4dc7dafdcd 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/handlers/cred_proposal_handler.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/handlers/cred_proposal_handler.py @@ -1,6 +1,6 @@ """Credential proposal message handler.""" -from .....indy.issuer import IndyIssuerError +from .....anoncreds.issuer import AnonCredsIssuerError from .....ledger.error import LedgerError from .....messaging.base_handler import BaseHandler, HandlerException from .....messaging.models.base import BaseModelError @@ -69,7 +69,7 @@ async def handle(self, context: RequestContext, responder: BaseResponder): await responder.send_reply(cred_offer_message) except ( BaseModelError, - IndyIssuerError, + AnonCredsIssuerError, LedgerError, StorageError, V20CredManagerError, diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/handlers/cred_request_handler.py b/aries_cloudagent/protocols/issue_credential/v2_0/handlers/cred_request_handler.py index 1bdf7671e9..af1ab58956 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/handlers/cred_request_handler.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/handlers/cred_request_handler.py @@ -1,7 +1,7 @@ """Credential request message handler.""" from .....core.oob_processor import OobMessageProcessor -from .....indy.issuer import IndyIssuerError +from .....anoncreds.issuer import AnonCredsIssuerError from .....ledger.error import LedgerError from .....messaging.base_handler import BaseHandler, HandlerException from .....messaging.models.base import BaseModelError @@ -80,7 +80,7 @@ async def handle(self, context: RequestContext, responder: BaseResponder): await responder.send_reply(cred_issue_message) except ( BaseModelError, - IndyIssuerError, + AnonCredsIssuerError, LedgerError, StorageError, V20CredManagerError, diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/handlers/tests/test_cred_issue_handler.py b/aries_cloudagent/protocols/issue_credential/v2_0/handlers/tests/test_cred_issue_handler.py index 2a47af1d5d..96d3c3793e 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/handlers/tests/test_cred_issue_handler.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/handlers/tests/test_cred_issue_handler.py @@ -103,7 +103,7 @@ async def test_called_auto_store_x(self): ), store_credential=async_mock.CoroutineMock( side_effect=[ - test_module.IndyHolderError, + test_module.AnonCredsHolderError, test_module.StorageError(), ] ), diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/handlers/tests/test_cred_offer_handler.py b/aries_cloudagent/protocols/issue_credential/v2_0/handlers/tests/test_cred_offer_handler.py index 66e295ce34..58687b3def 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/handlers/tests/test_cred_offer_handler.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/handlers/tests/test_cred_offer_handler.py @@ -106,7 +106,7 @@ async def test_called_auto_request_x(self): ) ) mock_cred_mgr.return_value.create_request = async_mock.CoroutineMock( - side_effect=test_module.IndyHolderError() + side_effect=test_module.AnonCredsHolderError() ) request_context.message = V20CredOffer() diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/handlers/tests/test_cred_proposal_handler.py b/aries_cloudagent/protocols/issue_credential/v2_0/handlers/tests/test_cred_proposal_handler.py index daaf4c0d79..2f7766d360 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/handlers/tests/test_cred_proposal_handler.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/handlers/tests/test_cred_proposal_handler.py @@ -80,7 +80,7 @@ async def test_called_auto_offer_x(self): ) mock_cred_mgr.return_value.receive_proposal.return_value.auto_offer = True mock_cred_mgr.return_value.create_offer = async_mock.CoroutineMock( - side_effect=test_module.IndyIssuerError() + side_effect=test_module.AnonCredsIssuerError() ) request_context.message = V20CredProposal() diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/handlers/tests/test_cred_request_handler.py b/aries_cloudagent/protocols/issue_credential/v2_0/handlers/tests/test_cred_request_handler.py index 25edba8b36..510c2a9098 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/handlers/tests/test_cred_request_handler.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/handlers/tests/test_cred_request_handler.py @@ -115,7 +115,7 @@ async def test_called_auto_issue_x(self): ) mock_cred_mgr.return_value.receive_request.return_value.auto_issue = True mock_cred_mgr.return_value.issue_credential = async_mock.CoroutineMock( - side_effect=test_module.IndyIssuerError() + side_effect=test_module.AnonCredsIssuerError() ) request_context.message = V20CredRequest() diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/routes.py b/aries_cloudagent/protocols/issue_credential/v2_0/routes.py index e3a066d339..735dd9250a 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/routes.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/routes.py @@ -20,8 +20,8 @@ from ....admin.request_context import AdminRequestContext from ....connections.models.conn_record import ConnRecord from ....core.profile import Profile -from ....indy.holder import IndyHolderError -from ....indy.issuer import IndyIssuerError +from ....anoncreds.holder import AnonCredsHolderError +from ....anoncreds.issuer import AnonCredsIssuerError from ....ledger.error import LedgerError from ....messaging.decorators.attach_decorator import AttachDecorator from ....messaging.models.base import BaseModelError @@ -966,7 +966,7 @@ async def credential_exchange_send_free_offer(request: web.BaseRequest): except ( BaseModelError, - IndyIssuerError, + AnonCredsIssuerError, LedgerError, StorageNotFoundError, V20CredFormatError, @@ -1071,7 +1071,7 @@ async def credential_exchange_send_bound_offer(request: web.BaseRequest): except ( BaseModelError, - IndyIssuerError, + AnonCredsIssuerError, LedgerError, StorageError, V20CredFormatError, @@ -1177,7 +1177,7 @@ async def credential_exchange_send_free_request(request: web.BaseRequest): except ( BaseModelError, - IndyHolderError, + AnonCredsHolderError, LedgerError, StorageError, V20CredManagerError, @@ -1286,7 +1286,7 @@ async def credential_exchange_send_bound_request(request: web.BaseRequest): except ( BaseModelError, - IndyHolderError, + AnonCredsHolderError, LedgerError, StorageError, V20CredFormatError, @@ -1380,7 +1380,7 @@ async def credential_exchange_issue(request: web.BaseRequest): except ( BaseModelError, - IndyIssuerError, + AnonCredsIssuerError, LedgerError, StorageError, V20CredFormatError, @@ -1469,7 +1469,7 @@ async def credential_exchange_store(request: web.BaseRequest): cred_ex_record = await cred_manager.store_credential(cred_ex_record, cred_id) except ( - IndyHolderError, + AnonCredsHolderError, StorageError, V20CredManagerError, ) as err: # treat failure to store as mangled on receipt hence protocol error diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/tests/test_manager.py b/aries_cloudagent/protocols/issue_credential/v2_0/tests/test_manager.py index 55d654c5f1..45b42b47be 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/tests/test_manager.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/tests/test_manager.py @@ -9,7 +9,7 @@ from .....cache.base import BaseCache from .....cache.in_memory import InMemoryCache from .....core.in_memory import InMemoryProfile -from .....indy.issuer import IndyIssuer +from .....anoncreds.issuer import AnonCredsIssuer from .....messaging.decorators.thread_decorator import ThreadDecorator from .....messaging.decorators.attach_decorator import AttachDecorator from .....messaging.responder import BaseResponder, MockResponder @@ -973,7 +973,7 @@ async def test_issue_credential(self): issuer.create_credential = async_mock.CoroutineMock( return_value=(json.dumps(INDY_CRED), cred_rev_id) ) - self.context.injector.bind_instance(IndyIssuer, issuer) + self.context.injector.bind_instance(AnonCredsIssuer, issuer) with async_mock.patch.object( V20CredExRecord, "save", autospec=True diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/tests/test_routes.py b/aries_cloudagent/protocols/issue_credential/v2_0/tests/test_routes.py index 845f21555c..54d1909d88 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/tests/test_routes.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/tests/test_routes.py @@ -1340,7 +1340,7 @@ async def test_credential_exchange_issue_rev_reg_full(self): mock_conn_rec.retrieve_by_id.return_value.is_ready = True mock_issue_cred = async_mock.CoroutineMock( - side_effect=test_module.IndyIssuerError() + side_effect=test_module.AnonCredsIssuerError() ) mock_cred_mgr.return_value.issue_credential = mock_issue_cred diff --git a/aries_cloudagent/protocols/present_proof/indy/pres_exch_handler.py b/aries_cloudagent/protocols/present_proof/indy/pres_exch_handler.py index 4bff88cc89..fd627cf83e 100644 --- a/aries_cloudagent/protocols/present_proof/indy/pres_exch_handler.py +++ b/aries_cloudagent/protocols/present_proof/indy/pres_exch_handler.py @@ -7,8 +7,8 @@ from ....core.error import BaseError from ....core.profile import Profile -from ....indy.holder import IndyHolder, IndyHolderError -from ....indy.models.xform import indy_proof_req2non_revoc_intervals +from ....anoncreds.holder import AnonCredsHolder, AnonCredsHolderError +from ....anoncreds.models.xform import indy_proof_req2non_revoc_intervals from ....ledger.multiple_ledger.ledger_requests_executor import ( GET_SCHEMA, GET_REVOC_REG_DELTA, @@ -46,7 +46,7 @@ async def return_presentation( ) -> dict: """Return Indy proof request as dict.""" # Get all credentials for this presentation - holder = self._profile.inject(IndyHolder) + holder = self._profile.inject(AnonCredsHolder) credentials = {} # extract credential ids and non_revoked @@ -189,7 +189,7 @@ async def return_presentation( tails_local_path, ) ) - except IndyHolderError as e: + except AnonCredsHolderError as e: LOGGER.error( f"Failed to create revocation state: {e.error_code}, {e.message}" ) diff --git a/aries_cloudagent/protocols/present_proof/v1_0/handlers/presentation_request_handler.py b/aries_cloudagent/protocols/present_proof/v1_0/handlers/presentation_request_handler.py index 1736d22843..c49f4ba2fd 100644 --- a/aries_cloudagent/protocols/present_proof/v1_0/handlers/presentation_request_handler.py +++ b/aries_cloudagent/protocols/present_proof/v1_0/handlers/presentation_request_handler.py @@ -1,8 +1,8 @@ """Presentation request message handler.""" from .....core.oob_processor import OobMessageProcessor -from .....indy.holder import IndyHolder, IndyHolderError -from .....indy.models.xform import indy_proof_req_preview2indy_requested_creds +from .....anoncreds.holder import AnonCredsHolder, AnonCredsHolderError +from .....anoncreds.models.xform import indy_proof_req_preview2indy_requested_creds from .....ledger.error import LedgerError from .....messaging.base_handler import BaseHandler, HandlerException from .....messaging.models.base import BaseModelError @@ -122,7 +122,7 @@ async def handle(self, context: RequestContext, responder: BaseResponder): req_creds = await indy_proof_req_preview2indy_requested_creds( indy_proof_request, presentation_preview, - holder=context.inject(IndyHolder), + holder=context.inject(AnonCredsHolder), ) except ValueError as err: self._logger.warning(f"{err}") @@ -143,7 +143,7 @@ async def handle(self, context: RequestContext, responder: BaseResponder): await responder.send_reply(presentation_message) except ( BaseModelError, - IndyHolderError, + AnonCredsHolderError, LedgerError, StorageError, WalletNotFoundError, diff --git a/aries_cloudagent/protocols/present_proof/v1_0/handlers/tests/test_presentation_request_handler.py b/aries_cloudagent/protocols/present_proof/v1_0/handlers/tests/test_presentation_request_handler.py index 83329461a4..a834cd40d3 100644 --- a/aries_cloudagent/protocols/present_proof/v1_0/handlers/tests/test_presentation_request_handler.py +++ b/aries_cloudagent/protocols/present_proof/v1_0/handlers/tests/test_presentation_request_handler.py @@ -2,8 +2,8 @@ from ......core.oob_processor import OobMessageProcessor -from ......indy.holder import IndyHolder -from ......indy.models.pres_preview import ( +from ......anoncreds.holder import AnonCredsHolder +from ......anoncreds.models.pres_preview import ( IndyPresAttrSpec, IndyPresPredSpec, IndyPresPreview, @@ -236,7 +236,7 @@ async def test_called_auto_present(self): ) ) request_context.injector.bind_instance(OobMessageProcessor, mock_oob_processor) - request_context.injector.bind_instance(IndyHolder, mock_holder) + request_context.injector.bind_instance(AnonCredsHolder, mock_holder) with async_mock.patch.object( test_module, "PresentationManager", autospec=True @@ -326,7 +326,7 @@ async def test_called_auto_present_x(self): ) ) request_context.injector.bind_instance(OobMessageProcessor, mock_oob_processor) - request_context.injector.bind_instance(IndyHolder, mock_holder) + request_context.injector.bind_instance(AnonCredsHolder, mock_holder) with async_mock.patch.object( test_module, "PresentationManager", autospec=True @@ -342,7 +342,7 @@ async def test_called_auto_present_x(self): ) mock_pres_mgr.return_value.create_presentation = async_mock.CoroutineMock( - side_effect=test_module.IndyHolderError() + side_effect=test_module.AnonCredsHolderError() ) request_context.connection_ready = True @@ -405,7 +405,7 @@ async def test_called_auto_present_no_preview(self): ) ) request_context.injector.bind_instance(OobMessageProcessor, mock_oob_processor) - request_context.injector.bind_instance(IndyHolder, mock_holder) + request_context.injector.bind_instance(AnonCredsHolder, mock_holder) with async_mock.patch.object( test_module, "PresentationManager", autospec=True @@ -480,7 +480,7 @@ async def test_called_auto_present_pred_no_match(self): ) ) request_context.injector.bind_instance(OobMessageProcessor, mock_oob_processor) - request_context.injector.bind_instance(IndyHolder, mock_holder) + request_context.injector.bind_instance(AnonCredsHolder, mock_holder) with async_mock.patch.object( test_module, "PresentationManager", autospec=True @@ -553,7 +553,7 @@ async def test_called_auto_present_pred_single_match(self): ) ) request_context.injector.bind_instance(OobMessageProcessor, mock_oob_processor) - request_context.injector.bind_instance(IndyHolder, mock_holder) + request_context.injector.bind_instance(AnonCredsHolder, mock_holder) with async_mock.patch.object( test_module, "PresentationManager", autospec=True @@ -632,7 +632,7 @@ async def test_called_auto_present_pred_multi_match(self): ) ) ) - request_context.injector.bind_instance(IndyHolder, mock_holder) + request_context.injector.bind_instance(AnonCredsHolder, mock_holder) request_context.injector.bind_instance(OobMessageProcessor, mock_oob_processor) with async_mock.patch.object( @@ -764,7 +764,7 @@ async def test_called_auto_present_multi_cred_match_reft(self): ) ) request_context.injector.bind_instance(OobMessageProcessor, mock_oob_processor) - request_context.injector.bind_instance(IndyHolder, mock_holder) + request_context.injector.bind_instance(AnonCredsHolder, mock_holder) with async_mock.patch.object( test_module, "PresentationManager", autospec=True @@ -870,7 +870,7 @@ async def test_called_auto_present_bait_and_switch(self): mock_holder = async_mock.MagicMock( get_credentials_for_presentation_request_by_referent=by_reft ) - request_context.injector.bind_instance(IndyHolder, mock_holder) + request_context.injector.bind_instance(AnonCredsHolder, mock_holder) mock_oob_processor = async_mock.MagicMock( find_oob_record_for_inbound_message=async_mock.CoroutineMock( diff --git a/aries_cloudagent/protocols/present_proof/v1_0/manager.py b/aries_cloudagent/protocols/present_proof/v1_0/manager.py index 540db13966..376d69f93c 100644 --- a/aries_cloudagent/protocols/present_proof/v1_0/manager.py +++ b/aries_cloudagent/protocols/present_proof/v1_0/manager.py @@ -8,7 +8,7 @@ from ....connections.models.conn_record import ConnRecord from ....core.error import BaseError from ....core.profile import Profile -from ....indy.verifier import IndyVerifier +from ....anoncreds.verifier import AnonCredsVerifier from ....messaging.decorators.attach_decorator import AttachDecorator from ....messaging.responder import BaseResponder from ....storage.error import StorageNotFoundError @@ -418,7 +418,7 @@ async def verify_presentation( rev_reg_entries, ) = await indy_handler.process_pres_identifiers(indy_proof["identifiers"]) - verifier = self._profile.inject(IndyVerifier) + verifier = self._profile.inject(AnonCredsVerifier) (verified_bool, verified_msgs) = await verifier.verify_presentation( dict( indy_proof_request diff --git a/aries_cloudagent/protocols/present_proof/v1_0/messages/presentation_proposal.py b/aries_cloudagent/protocols/present_proof/v1_0/messages/presentation_proposal.py index 872961d738..905e7c8afd 100644 --- a/aries_cloudagent/protocols/present_proof/v1_0/messages/presentation_proposal.py +++ b/aries_cloudagent/protocols/present_proof/v1_0/messages/presentation_proposal.py @@ -2,7 +2,7 @@ from marshmallow import EXCLUDE, fields -from .....indy.models.pres_preview import IndyPresPreview, IndyPresPreviewSchema +from .....anoncreds.models.pres_preview import IndyPresPreview, IndyPresPreviewSchema from .....messaging.agent_message import AgentMessage, AgentMessageSchema from ..message_types import PRESENTATION_PROPOSAL, PROTOCOL_PACKAGE diff --git a/aries_cloudagent/protocols/present_proof/v1_0/messages/tests/test_presentation.py b/aries_cloudagent/protocols/present_proof/v1_0/messages/tests/test_presentation.py index ae4a817153..86c513da37 100644 --- a/aries_cloudagent/protocols/present_proof/v1_0/messages/tests/test_presentation.py +++ b/aries_cloudagent/protocols/present_proof/v1_0/messages/tests/test_presentation.py @@ -2,7 +2,7 @@ from datetime import datetime, timezone from unittest import TestCase -from ......indy.models.pres_preview import PRESENTATION_PREVIEW +from ......anoncreds.models.pres_preview import PRESENTATION_PREVIEW from ......messaging.decorators.attach_decorator import AttachDecorator from ......messaging.util import str_to_datetime, str_to_epoch diff --git a/aries_cloudagent/protocols/present_proof/v1_0/messages/tests/test_presentation_proposal.py b/aries_cloudagent/protocols/present_proof/v1_0/messages/tests/test_presentation_proposal.py index 2c43f97269..cd2130a363 100644 --- a/aries_cloudagent/protocols/present_proof/v1_0/messages/tests/test_presentation_proposal.py +++ b/aries_cloudagent/protocols/present_proof/v1_0/messages/tests/test_presentation_proposal.py @@ -1,6 +1,6 @@ from unittest import TestCase -from ......indy.models.pres_preview import ( +from ......anoncreds.models.pres_preview import ( IndyPresAttrSpec, IndyPresPredSpec, IndyPresPreview, diff --git a/aries_cloudagent/protocols/present_proof/v1_0/messages/tests/test_presentation_request.py b/aries_cloudagent/protocols/present_proof/v1_0/messages/tests/test_presentation_request.py index c1e3ebcdaf..e9acf98ad8 100644 --- a/aries_cloudagent/protocols/present_proof/v1_0/messages/tests/test_presentation_request.py +++ b/aries_cloudagent/protocols/present_proof/v1_0/messages/tests/test_presentation_request.py @@ -2,7 +2,7 @@ from datetime import datetime, timezone from unittest import TestCase -from ......indy.models.pres_preview import PRESENTATION_PREVIEW +from ......anoncreds.models.pres_preview import PRESENTATION_PREVIEW from ......messaging.decorators.attach_decorator import AttachDecorator from ......messaging.util import str_to_datetime, str_to_epoch diff --git a/aries_cloudagent/protocols/present_proof/v1_0/models/presentation_exchange.py b/aries_cloudagent/protocols/present_proof/v1_0/models/presentation_exchange.py index 80db45f86c..bae76bde6a 100644 --- a/aries_cloudagent/protocols/present_proof/v1_0/models/presentation_exchange.py +++ b/aries_cloudagent/protocols/present_proof/v1_0/models/presentation_exchange.py @@ -7,8 +7,8 @@ from marshmallow import fields, validate from .....core.profile import ProfileSession -from .....indy.models.proof import IndyProof, IndyProofSchema -from .....indy.models.proof_request import IndyProofRequest, IndyProofRequestSchema +from .....anoncreds.models.proof import IndyProof, IndyProofSchema +from .....anoncreds.models.proof_request import IndyProofRequest, IndyProofRequestSchema from .....messaging.models.base_record import BaseExchangeRecord, BaseExchangeSchema from .....messaging.valid import UUIDFour from .....storage.base import StorageError diff --git a/aries_cloudagent/protocols/present_proof/v1_0/models/tests/test_record.py b/aries_cloudagent/protocols/present_proof/v1_0/models/tests/test_record.py index 13d9e5aac3..57447b9477 100644 --- a/aries_cloudagent/protocols/present_proof/v1_0/models/tests/test_record.py +++ b/aries_cloudagent/protocols/present_proof/v1_0/models/tests/test_record.py @@ -1,7 +1,7 @@ from asynctest import mock as async_mock, TestCase as AsyncTestCase from ......core.in_memory import InMemoryProfile -from ......indy.models.pres_preview import ( +from ......anoncreds.models.pres_preview import ( IndyPresAttrSpec, IndyPresPredSpec, IndyPresPreview, diff --git a/aries_cloudagent/protocols/present_proof/v1_0/routes.py b/aries_cloudagent/protocols/present_proof/v1_0/routes.py index f13d757cfd..901abc86fd 100644 --- a/aries_cloudagent/protocols/present_proof/v1_0/routes.py +++ b/aries_cloudagent/protocols/present_proof/v1_0/routes.py @@ -14,12 +14,12 @@ from ....admin.request_context import AdminRequestContext from ....connections.models.conn_record import ConnRecord -from ....indy.holder import IndyHolder, IndyHolderError -from ....indy.models.cred_precis import IndyCredPrecisSchema -from ....indy.models.proof import IndyPresSpecSchema -from ....indy.models.proof_request import IndyProofRequestSchema -from ....indy.models.pres_preview import IndyPresPreview, IndyPresPreviewSchema -from ....indy.util import generate_pr_nonce +from ....anoncreds.holder import AnonCredsHolder, AnonCredsHolderError +from ....anoncreds.models.cred_precis import IndyCredPrecisSchema +from ....anoncreds.models.proof import IndyPresSpecSchema +from ....anoncreds.models.proof_request import IndyProofRequestSchema +from ....anoncreds.models.pres_preview import IndyPresPreview, IndyPresPreviewSchema +from ....anoncreds.util import generate_pr_nonce from ....ledger.error import LedgerError from ....messaging.decorators.attach_decorator import AttachDecorator from ....messaging.models.base import BaseModelError @@ -340,7 +340,7 @@ async def presentation_exchange_credentials_list(request: web.BaseRequest): start = int(start) if isinstance(start, str) else 0 count = int(count) if isinstance(count, str) else 10 - holder = profile.inject(IndyHolder) + holder = profile.inject(AnonCredsHolder) try: credentials = await holder.get_credentials_for_presentation_request_by_referent( pres_ex_record._presentation_request.ser, @@ -349,7 +349,7 @@ async def presentation_exchange_credentials_list(request: web.BaseRequest): count, extra_query, ) - except IndyHolderError as err: + except AnonCredsHolderError as err: if pres_ex_record: async with profile.session() as session: await pres_ex_record.save_error_state(session, reason=err.roll_up) @@ -782,7 +782,7 @@ async def presentation_exchange_send_presentation(request: web.BaseRequest): result = pres_ex_record.serialize() except ( BaseModelError, - IndyHolderError, + AnonCredsHolderError, LedgerError, StorageError, WalletNotFoundError, diff --git a/aries_cloudagent/protocols/present_proof/v1_0/tests/test_manager.py b/aries_cloudagent/protocols/present_proof/v1_0/tests/test_manager.py index 044c21d317..452c89b0b6 100644 --- a/aries_cloudagent/protocols/present_proof/v1_0/tests/test_manager.py +++ b/aries_cloudagent/protocols/present_proof/v1_0/tests/test_manager.py @@ -9,17 +9,17 @@ ) from .....core.in_memory import InMemoryProfile -from .....indy.holder import IndyHolder, IndyHolderError -from .....indy.issuer import IndyIssuer -from .....indy.sdk.holder import IndySdkHolder -from .....indy.models.xform import indy_proof_req_preview2indy_requested_creds -from .....indy.models.pres_preview import ( +from .....anoncreds.holder import AnonCredsHolder, AnonCredsHolderError +from .....anoncreds.issuer import AnonCredsIssuer +from .....anoncreds.sdk.holder import IndySdkHolder +from .....anoncreds.models.xform import indy_proof_req_preview2indy_requested_creds +from .....anoncreds.models.pres_preview import ( IndyPresAttrSpec, IndyPresPreview, IndyPresPredSpec, ) -from .....indy.sdk.verifier import IndySdkVerifier -from .....indy.verifier import IndyVerifier +from .....anoncreds.sdk.verifier import IndySdkVerifier +from .....anoncreds.verifier import AnonCredsVerifier from .....ledger.base import BaseLedger from .....ledger.multiple_ledger.ledger_requests_executor import ( IndyLedgerRequestsExecutor, @@ -275,7 +275,7 @@ async def setUp(self): ) ), ) - Holder = async_mock.MagicMock(IndyHolder, autospec=True) + Holder = async_mock.MagicMock(AnonCredsHolder, autospec=True) self.holder = Holder() get_creds = async_mock.CoroutineMock( return_value=( @@ -312,14 +312,14 @@ async def setUp(self): } ) ) - injector.bind_instance(IndyHolder, self.holder) + injector.bind_instance(AnonCredsHolder, self.holder) - Verifier = async_mock.MagicMock(IndyVerifier, autospec=True) + Verifier = async_mock.MagicMock(AnonCredsVerifier, autospec=True) self.verifier = Verifier() self.verifier.verify_presentation = async_mock.CoroutineMock( return_value=("true", []) ) - injector.bind_instance(IndyVerifier, self.verifier) + injector.bind_instance(AnonCredsVerifier, self.verifier) self.manager = PresentationManager(self.profile) @@ -610,7 +610,7 @@ async def test_create_presentation_no_revocation(self): exchange_in.presentation_request = indy_proof_req - Holder = async_mock.MagicMock(IndyHolder, autospec=True) + Holder = async_mock.MagicMock(AnonCredsHolder, autospec=True) self.holder = Holder() get_creds = async_mock.CoroutineMock( return_value=( @@ -636,7 +636,7 @@ async def test_create_presentation_no_revocation(self): ) ) self.holder.create_presentation = async_mock.CoroutineMock(return_value="{}") - self.profile.context.injector.bind_instance(IndyHolder, self.holder) + self.profile.context.injector.bind_instance(AnonCredsHolder, self.holder) with async_mock.patch.object( V10PresentationExchange, "save", autospec=True @@ -676,7 +676,7 @@ async def test_create_presentation_bad_revoc_state(self): exchange_in.presentation_request = indy_proof_req - Holder = async_mock.MagicMock(IndyHolder, autospec=True) + Holder = async_mock.MagicMock(AnonCredsHolder, autospec=True) self.holder = Holder() get_creds = async_mock.CoroutineMock( return_value=( @@ -704,9 +704,9 @@ async def test_create_presentation_bad_revoc_state(self): ) self.holder.create_presentation = async_mock.CoroutineMock(return_value="{}") self.holder.create_revocation_state = async_mock.CoroutineMock( - side_effect=IndyHolderError("Problem", {"message": "Nope"}) + side_effect=AnonCredsHolderError("Problem", {"message": "Nope"}) ) - self.profile.context.injector.bind_instance(IndyHolder, self.holder) + self.profile.context.injector.bind_instance(AnonCredsHolder, self.holder) more_magic_rr = async_mock.MagicMock( get_or_fetch_local_tails_path=async_mock.CoroutineMock( @@ -730,7 +730,7 @@ async def test_create_presentation_bad_revoc_state(self): indy_proof_req, holder=self.holder ) - with self.assertRaises(IndyHolderError): + with self.assertRaises(AnonCredsHolderError): await self.manager.create_presentation(exchange_in, req_creds) async def test_create_presentation_multi_matching_proposal_creds_names(self): @@ -744,7 +744,7 @@ async def test_create_presentation_multi_matching_proposal_creds_names(self): exchange_in.presentation_request = indy_proof_req - Holder = async_mock.MagicMock(IndyHolder, autospec=True) + Holder = async_mock.MagicMock(AnonCredsHolder, autospec=True) self.holder = Holder() get_creds = async_mock.CoroutineMock( return_value=( @@ -793,7 +793,7 @@ async def test_create_presentation_multi_matching_proposal_creds_names(self): } ) ) - self.profile.context.injector.bind_instance(IndyHolder, self.holder) + self.profile.context.injector.bind_instance(AnonCredsHolder, self.holder) more_magic_rr = async_mock.MagicMock( get_or_fetch_local_tails_path=async_mock.CoroutineMock( diff --git a/aries_cloudagent/protocols/present_proof/v1_0/tests/test_routes.py b/aries_cloudagent/protocols/present_proof/v1_0/tests/test_routes.py index 77e3ea1ca7..a9e859ace1 100644 --- a/aries_cloudagent/protocols/present_proof/v1_0/tests/test_routes.py +++ b/aries_cloudagent/protocols/present_proof/v1_0/tests/test_routes.py @@ -5,9 +5,9 @@ from marshmallow import ValidationError from .....admin.request_context import AdminRequestContext -from .....indy.holder import IndyHolder -from .....indy.models.proof_request import IndyProofReqAttrSpecSchema -from .....indy.verifier import IndyVerifier +from .....anoncreds.holder import AnonCredsHolder +from .....anoncreds.models.proof_request import IndyProofReqAttrSpecSchema +from .....anoncreds.verifier import AnonCredsVerifier from .....ledger.base import BaseLedger from .....storage.error import StorageNotFoundError @@ -139,10 +139,10 @@ async def test_presentation_exchange_credentials_x(self): self.request.query = {"extra_query": {}} returned_credentials = [{"name": "Credential1"}, {"name": "Credential2"}] self.profile.context.injector.bind_instance( - IndyHolder, + AnonCredsHolder, async_mock.MagicMock( get_credentials_for_presentation_request_by_referent=( - async_mock.CoroutineMock(side_effect=test_module.IndyHolderError()) + async_mock.CoroutineMock(side_effect=test_module.AnonCredsHolderError()) ) ), ) @@ -172,7 +172,7 @@ async def test_presentation_exchange_credentials_list_single_referent(self): returned_credentials = [{"name": "Credential1"}, {"name": "Credential2"}] self.profile.context.injector.bind_instance( - IndyHolder, + AnonCredsHolder, async_mock.MagicMock( get_credentials_for_presentation_request_by_referent=async_mock.CoroutineMock( return_value=returned_credentials @@ -209,7 +209,7 @@ async def test_presentation_exchange_credentials_list_multiple_referents(self): returned_credentials = [{"name": "Credential1"}, {"name": "Credential2"}] self.profile.context.injector.bind_instance( - IndyHolder, + AnonCredsHolder, async_mock.MagicMock( get_credentials_for_presentation_request_by_referent=( async_mock.CoroutineMock(return_value=returned_credentials) @@ -322,7 +322,7 @@ async def test_presentation_exchange_send_proposal(self): "aries_cloudagent.protocols.present_proof.v1_0.manager.PresentationManager", autospec=True, ) as mock_presentation_manager, async_mock.patch( - "aries_cloudagent.indy.models.pres_preview.IndyPresPreview", + "aries_cloudagent.anoncreds.models.pres_preview.IndyPresPreview", autospec=True, ) as mock_preview: # Since we are mocking import @@ -368,7 +368,7 @@ async def test_presentation_exchange_send_proposal_not_ready(self): "aries_cloudagent.connections.models.conn_record.ConnRecord", autospec=True, ) as mock_connection_record, async_mock.patch( - "aries_cloudagent.indy.models.pres_preview.IndyPresPreview", + "aries_cloudagent.anoncreds.models.pres_preview.IndyPresPreview", autospec=True, ) as mock_preview, async_mock.patch( ( @@ -396,7 +396,7 @@ async def test_presentation_exchange_send_proposal_x(self): "aries_cloudagent.protocols.present_proof.v1_0.manager.PresentationManager", autospec=True, ) as mock_presentation_manager, async_mock.patch( - "aries_cloudagent.indy.models.pres_preview.IndyPresPreview", + "aries_cloudagent.anoncreds.models.pres_preview.IndyPresPreview", autospec=True, ) as mock_preview: # Since we are mocking import @@ -426,7 +426,7 @@ async def test_presentation_exchange_create_request(self): "aries_cloudagent.protocols.present_proof.v1_0.manager.PresentationManager", autospec=True, ) as mock_presentation_manager, async_mock.patch( - "aries_cloudagent.indy.models.pres_preview.IndyPresPreview", + "aries_cloudagent.anoncreds.models.pres_preview.IndyPresPreview", autospec=True, ) as mock_preview, async_mock.patch.object( test_module, "PresentationRequest", autospec=True @@ -440,7 +440,7 @@ async def test_presentation_exchange_create_request(self): ), autospec=True, ) as mock_presentation_exchange, async_mock.patch( - "aries_cloudagent.indy.util.generate_pr_nonce", + "aries_cloudagent.anoncreds.util.generate_pr_nonce", autospec=True, ) as mock_generate_nonce: # Since we are mocking import @@ -479,7 +479,7 @@ async def test_presentation_exchange_create_request_x(self): "aries_cloudagent.protocols.present_proof.v1_0.manager.PresentationManager", autospec=True, ) as mock_presentation_manager, async_mock.patch( - "aries_cloudagent.indy.models.pres_preview.IndyPresPreview", + "aries_cloudagent.anoncreds.models.pres_preview.IndyPresPreview", autospec=True, ) as mock_preview, async_mock.patch.object( test_module, "PresentationRequest", autospec=True @@ -493,7 +493,7 @@ async def test_presentation_exchange_create_request_x(self): ), autospec=True, ) as mock_presentation_exchange, async_mock.patch( - "aries_cloudagent.indy.util.generate_pr_nonce", + "aries_cloudagent.anoncreds.util.generate_pr_nonce", autospec=True, ) as mock_generate_nonce: # Since we are mocking import @@ -530,10 +530,10 @@ async def test_presentation_exchange_send_free_request(self): "aries_cloudagent.protocols.present_proof.v1_0.manager.PresentationManager", autospec=True, ) as mock_presentation_manager, async_mock.patch( - "aries_cloudagent.indy.util.generate_pr_nonce", + "aries_cloudagent.anoncreds.util.generate_pr_nonce", autospec=True, ) as mock_generate_nonce, async_mock.patch( - "aries_cloudagent.indy.models.pres_preview.IndyPresPreview", + "aries_cloudagent.anoncreds.models.pres_preview.IndyPresPreview", autospec=True, ) as mock_preview, async_mock.patch.object( test_module, "PresentationRequest", autospec=True @@ -630,7 +630,7 @@ async def test_presentation_exchange_send_free_request_x(self): "aries_cloudagent.protocols.present_proof.v1_0.manager.PresentationManager", autospec=True, ) as mock_presentation_manager, async_mock.patch( - "aries_cloudagent.indy.util.generate_pr_nonce", + "aries_cloudagent.anoncreds.util.generate_pr_nonce", autospec=True, ) as mock_generate_nonce, async_mock.patch.object( test_module, "IndyPresPreview", autospec=True @@ -685,7 +685,7 @@ async def test_presentation_exchange_send_bound_request(self): ), ) self.profile.context.injector.bind_instance( - IndyVerifier, + AnonCredsVerifier, async_mock.MagicMock( verify_presentation=async_mock.CoroutineMock(), ), @@ -698,7 +698,7 @@ async def test_presentation_exchange_send_bound_request(self): "aries_cloudagent.protocols.present_proof.v1_0.manager.PresentationManager", autospec=True, ) as mock_presentation_manager, async_mock.patch( - "aries_cloudagent.indy.util.generate_pr_nonce", + "aries_cloudagent.anoncreds.util.generate_pr_nonce", autospec=True, ) as mock_generate_nonce, async_mock.patch.object( test_module, "IndyPresPreview", autospec=True @@ -757,7 +757,7 @@ async def test_presentation_exchange_send_bound_request_not_found(self): "aries_cloudagent.protocols.present_proof.v1_0.manager.PresentationManager", autospec=True, ) as mock_presentation_manager, async_mock.patch( - "aries_cloudagent.indy.util.generate_pr_nonce", + "aries_cloudagent.anoncreds.util.generate_pr_nonce", autospec=True, ) as mock_generate_nonce, async_mock.patch.object( test_module, "IndyPresPreview", autospec=True @@ -801,7 +801,7 @@ async def test_presentation_exchange_send_bound_request_not_ready(self): "aries_cloudagent.protocols.present_proof.v1_0.manager.PresentationManager", autospec=True, ) as mock_presentation_manager, async_mock.patch( - "aries_cloudagent.indy.util.generate_pr_nonce", + "aries_cloudagent.anoncreds.util.generate_pr_nonce", autospec=True, ) as mock_generate_nonce, async_mock.patch.object( test_module, "IndyPresPreview", autospec=True @@ -884,7 +884,7 @@ async def test_presentation_exchange_send_bound_request_x(self): "aries_cloudagent.protocols.present_proof.v1_0.manager.PresentationManager", autospec=True, ) as mock_presentation_manager, async_mock.patch( - "aries_cloudagent.indy.util.generate_pr_nonce", + "aries_cloudagent.anoncreds.util.generate_pr_nonce", autospec=True, ) as mock_generate_nonce, async_mock.patch.object( test_module, "IndyPresPreview", autospec=True @@ -953,7 +953,7 @@ async def test_presentation_exchange_send_presentation(self): ), ) self.profile.context.injector.bind_instance( - IndyVerifier, + AnonCredsVerifier, async_mock.MagicMock( verify_presentation=async_mock.CoroutineMock(), ), @@ -1034,7 +1034,7 @@ async def test_presentation_exchange_send_presentation_not_found(self): "aries_cloudagent.protocols.present_proof.v1_0.manager.PresentationManager", autospec=True, ) as mock_presentation_manager, async_mock.patch( - "aries_cloudagent.indy.util.generate_pr_nonce", + "aries_cloudagent.anoncreds.util.generate_pr_nonce", autospec=True, ) as mock_generate_nonce, async_mock.patch.object( test_module, "IndyPresPreview", autospec=True @@ -1078,7 +1078,7 @@ async def test_presentation_exchange_send_presentation_not_ready(self): "aries_cloudagent.protocols.present_proof.v1_0.manager.PresentationManager", autospec=True, ) as mock_presentation_manager, async_mock.patch( - "aries_cloudagent.indy.util.generate_pr_nonce", + "aries_cloudagent.anoncreds.util.generate_pr_nonce", autospec=True, ) as mock_generate_nonce, async_mock.patch.object( test_module, "IndyPresPreview", autospec=True @@ -1152,7 +1152,7 @@ async def test_presentation_exchange_send_presentation_x(self): "aries_cloudagent.protocols.present_proof.v1_0.manager.PresentationManager", autospec=True, ) as mock_presentation_manager, async_mock.patch( - "aries_cloudagent.indy.util.generate_pr_nonce", + "aries_cloudagent.anoncreds.util.generate_pr_nonce", autospec=True, ) as mock_generate_nonce, async_mock.patch.object( test_module, "IndyPresPreview", autospec=True @@ -1205,10 +1205,10 @@ async def test_presentation_exchange_verify_presentation(self): "aries_cloudagent.protocols.present_proof.v1_0.manager.PresentationManager", autospec=True, ) as mock_presentation_manager, async_mock.patch( - "aries_cloudagent.indy.util.generate_pr_nonce", + "aries_cloudagent.anoncreds.util.generate_pr_nonce", autospec=True, ) as mock_generate_nonce, async_mock.patch( - "aries_cloudagent.indy.models.pres_preview.IndyPresPreview", + "aries_cloudagent.anoncreds.models.pres_preview.IndyPresPreview", autospec=True, ) as mock_preview, async_mock.patch.object( test_module, "PresentationRequest", autospec=True @@ -1304,7 +1304,7 @@ async def test_presentation_exchange_verify_presentation_x(self): ), ) self.profile.context.injector.bind_instance( - IndyVerifier, + AnonCredsVerifier, async_mock.MagicMock( verify_presentation=async_mock.CoroutineMock(), ), diff --git a/aries_cloudagent/protocols/present_proof/v2_0/formats/indy/handler.py b/aries_cloudagent/protocols/present_proof/v2_0/formats/indy/handler.py index 8f0a3e5057..802a928bbd 100644 --- a/aries_cloudagent/protocols/present_proof/v2_0/formats/indy/handler.py +++ b/aries_cloudagent/protocols/present_proof/v2_0/formats/indy/handler.py @@ -6,13 +6,13 @@ from marshmallow import RAISE from typing import Mapping, Tuple -from ......indy.holder import IndyHolder -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 ......indy.util import generate_pr_nonce -from ......indy.verifier import IndyVerifier +from ......anoncreds.holder import AnonCredsHolder +from ......anoncreds.models.predicate import Predicate +from ......anoncreds.models.proof import IndyProofSchema +from ......anoncreds.models.proof_request import IndyProofRequestSchema +from ......anoncreds.models.xform import indy_proof_req_preview2indy_requested_creds +from ......anoncreds.util import generate_pr_nonce +from ......anoncreds.verifier import AnonCredsVerifier from ......messaging.decorators.attach_decorator import AttachDecorator from ......messaging.util import canon @@ -143,7 +143,7 @@ async def create_pres( await indy_proof_req_preview2indy_requested_creds( indy_proof_request, preview=None, - holder=self._profile.inject(IndyHolder), + holder=self._profile.inject(AnonCredsHolder), ) ) except ValueError as err: @@ -328,7 +328,7 @@ async def verify_pres(self, pres_ex_record: V20PresExRecord) -> V20PresExRecord: rev_reg_entries, ) = await indy_handler.process_pres_identifiers(indy_proof["identifiers"]) - verifier = self._profile.inject(IndyVerifier) + verifier = self._profile.inject(AnonCredsVerifier) (verified, verified_msgs) = await verifier.verify_presentation( indy_proof_request, indy_proof, diff --git a/aries_cloudagent/protocols/present_proof/v2_0/handlers/pres_request_handler.py b/aries_cloudagent/protocols/present_proof/v2_0/handlers/pres_request_handler.py index 1d8abe88b6..6ad3729c51 100644 --- a/aries_cloudagent/protocols/present_proof/v2_0/handlers/pres_request_handler.py +++ b/aries_cloudagent/protocols/present_proof/v2_0/handlers/pres_request_handler.py @@ -1,7 +1,7 @@ """Presentation request message handler.""" from .....core.oob_processor import OobMessageProcessor -from .....indy.holder import IndyHolderError +from .....anoncreds.holder import AnonCredsHolderError from .....ledger.error import LedgerError from .....messaging.base_handler import BaseHandler, HandlerException from .....messaging.models.base import BaseModelError @@ -116,7 +116,7 @@ async def handle(self, context: RequestContext, responder: BaseResponder): await responder.send_reply(pres_message) except ( BaseModelError, - IndyHolderError, + AnonCredsHolderError, LedgerError, StorageError, WalletNotFoundError, diff --git a/aries_cloudagent/protocols/present_proof/v2_0/handlers/tests/test_pres_request_handler.py b/aries_cloudagent/protocols/present_proof/v2_0/handlers/tests/test_pres_request_handler.py index c0e28fa043..18e49355ca 100644 --- a/aries_cloudagent/protocols/present_proof/v2_0/handlers/tests/test_pres_request_handler.py +++ b/aries_cloudagent/protocols/present_proof/v2_0/handlers/tests/test_pres_request_handler.py @@ -2,7 +2,7 @@ from copy import deepcopy from ......core.oob_processor import OobMessageProcessor -from ......indy.holder import IndyHolder +from ......anoncreds.holder import AnonCredsHolder from ......messaging.decorators.attach_decorator import AttachDecorator from ......messaging.request_context import RequestContext from ......messaging.responder import MockResponder @@ -339,7 +339,7 @@ async def test_called_auto_present_x(self): ) ) ) - request_context.injector.bind_instance(IndyHolder, mock_holder) + request_context.injector.bind_instance(AnonCredsHolder, mock_holder) request_context.injector.bind_instance(OobMessageProcessor, mock_oob_processor) with async_mock.patch.object( @@ -356,7 +356,7 @@ async def test_called_auto_present_x(self): ) mock_pres_mgr.return_value.create_pres = async_mock.CoroutineMock( - side_effect=test_module.IndyHolderError() + side_effect=test_module.AnonCredsHolderError() ) request_context.connection_ready = True @@ -409,7 +409,7 @@ async def test_called_auto_present_indy(self): ) ) ) - request_context.injector.bind_instance(IndyHolder, mock_holder) + request_context.injector.bind_instance(AnonCredsHolder, mock_holder) with async_mock.patch.object( test_module, "V20PresManager", autospec=True @@ -553,7 +553,7 @@ async def test_called_auto_present_no_preview(self): ) ) ) - request_context.injector.bind_instance(IndyHolder, mock_holder) + request_context.injector.bind_instance(AnonCredsHolder, mock_holder) with async_mock.patch.object( test_module, "V20PresManager", autospec=True @@ -627,7 +627,7 @@ async def test_called_auto_present_pred_no_match(self): async_mock.CoroutineMock(return_value=[]) ) ) - request_context.injector.bind_instance(IndyHolder, mock_holder) + request_context.injector.bind_instance(AnonCredsHolder, mock_holder) with async_mock.patch.object( test_module, "V20PresManager", autospec=True @@ -691,7 +691,7 @@ async def test_called_auto_present_pred_single_match(self): ) ) ) - request_context.injector.bind_instance(IndyHolder, mock_holder) + request_context.injector.bind_instance(AnonCredsHolder, mock_holder) with async_mock.patch.object( test_module, "V20PresManager", autospec=True @@ -762,7 +762,7 @@ async def test_called_auto_present_pred_multi_match(self): ) ) ) - request_context.injector.bind_instance(IndyHolder, mock_holder) + request_context.injector.bind_instance(AnonCredsHolder, mock_holder) with async_mock.patch.object( test_module, "V20PresManager", autospec=True @@ -874,7 +874,7 @@ async def test_called_auto_present_multi_cred_match_reft(self): ) ) ) - request_context.injector.bind_instance(IndyHolder, mock_holder) + request_context.injector.bind_instance(AnonCredsHolder, mock_holder) px_rec_instance = test_module.V20PresExRecord( pres_proposal=pres_proposal.serialize(), diff --git a/aries_cloudagent/protocols/present_proof/v2_0/messages/tests/test_pres_format.py b/aries_cloudagent/protocols/present_proof/v2_0/messages/tests/test_pres_format.py index 90a62f5465..11ffac0b40 100644 --- a/aries_cloudagent/protocols/present_proof/v2_0/messages/tests/test_pres_format.py +++ b/aries_cloudagent/protocols/present_proof/v2_0/messages/tests/test_pres_format.py @@ -2,7 +2,7 @@ from marshmallow import ValidationError -from ......indy.models.pres_preview import ( +from ......anoncreds.models.pres_preview import ( IndyPresAttrSpec, IndyPresPreview, IndyPresPredSpec, diff --git a/aries_cloudagent/protocols/present_proof/v2_0/messages/tests/test_pres_proposal.py b/aries_cloudagent/protocols/present_proof/v2_0/messages/tests/test_pres_proposal.py index a2815c2ce4..4b48060e51 100644 --- a/aries_cloudagent/protocols/present_proof/v2_0/messages/tests/test_pres_proposal.py +++ b/aries_cloudagent/protocols/present_proof/v2_0/messages/tests/test_pres_proposal.py @@ -2,7 +2,7 @@ from unittest import TestCase -from ......indy.models.pres_preview import ( +from ......anoncreds.models.pres_preview import ( IndyPresAttrSpec, IndyPresPredSpec, IndyPresPreview, diff --git a/aries_cloudagent/protocols/present_proof/v2_0/messages/tests/test_pres_request.py b/aries_cloudagent/protocols/present_proof/v2_0/messages/tests/test_pres_request.py index 91b87bfa6e..7f082b3f6a 100644 --- a/aries_cloudagent/protocols/present_proof/v2_0/messages/tests/test_pres_request.py +++ b/aries_cloudagent/protocols/present_proof/v2_0/messages/tests/test_pres_request.py @@ -4,7 +4,7 @@ from datetime import datetime, timezone from unittest import TestCase -from ......indy.models.pres_preview import PRESENTATION_PREVIEW +from ......anoncreds.models.pres_preview import PRESENTATION_PREVIEW from ......messaging.decorators.attach_decorator import AttachDecorator from ......messaging.models.base import BaseModelError from ......messaging.util import str_to_datetime, str_to_epoch diff --git a/aries_cloudagent/protocols/present_proof/v2_0/models/tests/test_record.py b/aries_cloudagent/protocols/present_proof/v2_0/models/tests/test_record.py index c22a6ff23b..37eccf22b0 100644 --- a/aries_cloudagent/protocols/present_proof/v2_0/models/tests/test_record.py +++ b/aries_cloudagent/protocols/present_proof/v2_0/models/tests/test_record.py @@ -1,7 +1,7 @@ from asynctest import mock as async_mock, TestCase as AsyncTestCase from ......core.in_memory import InMemoryProfile -from ......indy.models.pres_preview import ( +from ......anoncreds.models.pres_preview import ( IndyPresAttrSpec, IndyPresPredSpec, IndyPresPreview, diff --git a/aries_cloudagent/protocols/present_proof/v2_0/routes.py b/aries_cloudagent/protocols/present_proof/v2_0/routes.py index 68a55087ef..4a521b6a55 100644 --- a/aries_cloudagent/protocols/present_proof/v2_0/routes.py +++ b/aries_cloudagent/protocols/present_proof/v2_0/routes.py @@ -15,11 +15,11 @@ from ....admin.request_context import AdminRequestContext from ....connections.models.conn_record import ConnRecord -from ....indy.holder import IndyHolder, IndyHolderError -from ....indy.models.cred_precis import IndyCredPrecisSchema -from ....indy.models.proof import IndyPresSpecSchema -from ....indy.models.proof_request import IndyProofRequestSchema -from ....indy.util import generate_pr_nonce +from ....anoncreds.holder import AnonCredsHolder, AnonCredsHolderError +from ....anoncreds.models.cred_precis import IndyCredPrecisSchema +from ....anoncreds.models.proof import IndyPresSpecSchema +from ....anoncreds.models.proof_request import IndyProofRequestSchema +from ....anoncreds.util import generate_pr_nonce from ....ledger.error import LedgerError from ....messaging.decorators.attach_decorator import AttachDecorator from ....messaging.models.base import BaseModelError @@ -479,7 +479,7 @@ async def present_proof_credentials_list(request: web.BaseRequest): start = int(start) if isinstance(start, str) else 0 count = int(count) if isinstance(count, str) else 10 - indy_holder = profile.inject(IndyHolder) + indy_holder = profile.inject(AnonCredsHolder) indy_credentials = [] # INDY try: @@ -496,7 +496,7 @@ async def present_proof_credentials_list(request: web.BaseRequest): extra_query, ) ) - except IndyHolderError as err: + except AnonCredsHolderError as err: if pres_ex_record: async with profile.session() as session: await pres_ex_record.save_error_state(session, reason=err.roll_up) @@ -1111,7 +1111,7 @@ async def present_proof_send_presentation(request: web.BaseRequest): result = pres_ex_record.serialize() except ( BaseModelError, - IndyHolderError, + AnonCredsHolderError, LedgerError, V20PresFormatHandlerError, StorageError, diff --git a/aries_cloudagent/protocols/present_proof/v2_0/tests/test_manager.py b/aries_cloudagent/protocols/present_proof/v2_0/tests/test_manager.py index 14a22ce4d8..f8130b83e6 100644 --- a/aries_cloudagent/protocols/present_proof/v2_0/tests/test_manager.py +++ b/aries_cloudagent/protocols/present_proof/v2_0/tests/test_manager.py @@ -6,14 +6,14 @@ from asynctest import mock as async_mock, TestCase as AsyncTestCase from .....core.in_memory import InMemoryProfile -from .....indy.holder import IndyHolder -from .....indy.models.xform import indy_proof_req_preview2indy_requested_creds -from .....indy.models.pres_preview import ( +from .....anoncreds.holder import AnonCredsHolder +from .....anoncreds.models.xform import indy_proof_req_preview2indy_requested_creds +from .....anoncreds.models.pres_preview import ( IndyPresAttrSpec, IndyPresPreview, IndyPresPredSpec, ) -from .....indy.verifier import IndyVerifier +from .....anoncreds.verifier import AnonCredsVerifier from .....ledger.base import BaseLedger from .....ledger.multiple_ledger.ledger_requests_executor import ( IndyLedgerRequestsExecutor, @@ -439,7 +439,7 @@ async def setUp(self): ), ) - Holder = async_mock.MagicMock(IndyHolder, autospec=True) + Holder = async_mock.MagicMock(AnonCredsHolder, autospec=True) self.holder = Holder() get_creds = async_mock.CoroutineMock( return_value=( @@ -476,14 +476,14 @@ async def setUp(self): } ) ) - injector.bind_instance(IndyHolder, self.holder) + injector.bind_instance(AnonCredsHolder, self.holder) - Verifier = async_mock.MagicMock(IndyVerifier, autospec=True) + Verifier = async_mock.MagicMock(AnonCredsVerifier, autospec=True) self.verifier = Verifier() self.verifier.verify_presentation = async_mock.CoroutineMock( return_value=("true", []) ) - injector.bind_instance(IndyVerifier, self.verifier) + injector.bind_instance(AnonCredsVerifier, self.verifier) self.manager = V20PresManager(self.profile) @@ -1005,7 +1005,7 @@ async def test_create_pres_no_revocation(self): ) px_rec_in = V20PresExRecord(pres_request=pres_request.serialize()) - Holder = async_mock.MagicMock(IndyHolder, autospec=True) + Holder = async_mock.MagicMock(AnonCredsHolder, autospec=True) self.holder = Holder() get_creds = async_mock.CoroutineMock( return_value=( @@ -1031,7 +1031,7 @@ async def test_create_pres_no_revocation(self): ) ) self.holder.create_presentation = async_mock.CoroutineMock(return_value="{}") - self.profile.context.injector.bind_instance(IndyHolder, self.holder) + self.profile.context.injector.bind_instance(AnonCredsHolder, self.holder) with async_mock.patch.object( V20PresExRecord, "save", autospec=True @@ -1090,7 +1090,7 @@ async def test_create_pres_bad_revoc_state(self): ) px_rec_in = V20PresExRecord(pres_request=pres_request.serialize()) - Holder = async_mock.MagicMock(IndyHolder, autospec=True) + Holder = async_mock.MagicMock(AnonCredsHolder, autospec=True) self.holder = Holder() get_creds = async_mock.CoroutineMock( return_value=( @@ -1118,11 +1118,11 @@ async def test_create_pres_bad_revoc_state(self): ) self.holder.create_presentation = async_mock.CoroutineMock(return_value="{}") self.holder.create_revocation_state = async_mock.CoroutineMock( - side_effect=test_indy_util_module.IndyHolderError( + side_effect=test_indy_util_module.AnonCredsHolderError( "Problem", {"message": "Nope"} ) ) - self.profile.context.injector.bind_instance(IndyHolder, self.holder) + self.profile.context.injector.bind_instance(AnonCredsHolder, self.holder) more_magic_rr = async_mock.MagicMock( get_or_fetch_local_tails_path=async_mock.CoroutineMock( @@ -1144,7 +1144,7 @@ async def test_create_pres_bad_revoc_state(self): return_value=mock_attach_decorator ) request_data = {} - with self.assertRaises(test_indy_util_module.IndyHolderError): + with self.assertRaises(test_indy_util_module.AnonCredsHolderError): await self.manager.create_pres(px_rec_in, request_data) async def test_create_pres_multi_matching_proposal_creds_names(self): @@ -1163,7 +1163,7 @@ async def test_create_pres_multi_matching_proposal_creds_names(self): ) px_rec_in = V20PresExRecord(pres_request=pres_request.serialize()) - Holder = async_mock.MagicMock(IndyHolder, autospec=True) + Holder = async_mock.MagicMock(AnonCredsHolder, autospec=True) self.holder = Holder() get_creds = async_mock.CoroutineMock( return_value=( @@ -1212,7 +1212,7 @@ async def test_create_pres_multi_matching_proposal_creds_names(self): } ) ) - self.profile.context.injector.bind_instance(IndyHolder, self.holder) + self.profile.context.injector.bind_instance(AnonCredsHolder, self.holder) more_magic_rr = async_mock.MagicMock( get_or_fetch_local_tails_path=async_mock.CoroutineMock( @@ -2202,7 +2202,7 @@ async def test_verify_pres_indy_and_dif(self): return_value=PresentationVerificationResult(verified=False) ), ), async_mock.patch.object( - IndyVerifier, + AnonCredsVerifier, "verify_presentation", async_mock.CoroutineMock( return_value=PresentationVerificationResult(verified=True) diff --git a/aries_cloudagent/protocols/present_proof/v2_0/tests/test_routes.py b/aries_cloudagent/protocols/present_proof/v2_0/tests/test_routes.py index 0d13acc826..97c4b7360a 100644 --- a/aries_cloudagent/protocols/present_proof/v2_0/tests/test_routes.py +++ b/aries_cloudagent/protocols/present_proof/v2_0/tests/test_routes.py @@ -6,9 +6,9 @@ from unittest.mock import ANY from .....admin.request_context import AdminRequestContext -from .....indy.holder import IndyHolder -from .....indy.models.proof_request import IndyProofReqAttrSpecSchema -from .....indy.verifier import IndyVerifier +from .....anoncreds.holder import AnonCredsHolder +from .....anoncreds.models.proof_request import IndyProofReqAttrSpecSchema +from .....anoncreds.verifier import AnonCredsVerifier from .....ledger.base import BaseLedger from .....storage.error import StorageNotFoundError from .....storage.vc_holder.base import VCHolder @@ -308,10 +308,10 @@ async def test_present_proof_credentials_x(self): self.request.query = {"extra_query": {}} returned_credentials = [{"name": "Credential1"}, {"name": "Credential2"}] self.profile.context.injector.bind_instance( - IndyHolder, + AnonCredsHolder, async_mock.MagicMock( get_credentials_for_presentation_request_by_referent=( - async_mock.CoroutineMock(side_effect=test_module.IndyHolderError()) + async_mock.CoroutineMock(side_effect=test_module.AnonCredsHolderError()) ) ), ) @@ -336,7 +336,7 @@ async def test_present_proof_credentials_list_single_referent(self): returned_credentials = [{"name": "Credential1"}, {"name": "Credential2"}] self.profile.context.injector.bind_instance( - IndyHolder, + AnonCredsHolder, async_mock.MagicMock( get_credentials_for_presentation_request_by_referent=( async_mock.CoroutineMock(return_value=returned_credentials) @@ -365,7 +365,7 @@ async def test_present_proof_credentials_list_multiple_referents(self): returned_credentials = [{"name": "Credential1"}, {"name": "Credential2"}] self.profile.context.injector.bind_instance( - IndyHolder, + AnonCredsHolder, async_mock.MagicMock( get_credentials_for_presentation_request_by_referent=( async_mock.CoroutineMock(return_value=returned_credentials) @@ -396,7 +396,7 @@ async def test_present_proof_credentials_list_dif(self): async_mock.MagicMock(cred_value={"name": "Credential2"}), ] self.profile.context.injector.bind_instance( - IndyHolder, + AnonCredsHolder, async_mock.MagicMock( get_credentials_for_presentation_request_by_referent=( async_mock.CoroutineMock() @@ -474,7 +474,7 @@ async def test_present_proof_credentials_list_dif_one_of_filter(self): ), ] self.profile.context.injector.bind_instance( - IndyHolder, + AnonCredsHolder, async_mock.MagicMock( get_credentials_for_presentation_request_by_referent=( async_mock.CoroutineMock() @@ -564,7 +564,7 @@ async def test_present_proof_credentials_dif_no_tag_query(self): async_mock.MagicMock(cred_value={"name": "Credential2"}), ] self.profile.context.injector.bind_instance( - IndyHolder, + AnonCredsHolder, async_mock.MagicMock( get_credentials_for_presentation_request_by_referent=( async_mock.CoroutineMock() @@ -644,7 +644,7 @@ async def test_present_proof_credentials_single_ldp_vp_claim_format(self): async_mock.MagicMock(cred_value={"name": "Credential2"}), ] self.profile.context.injector.bind_instance( - IndyHolder, + AnonCredsHolder, async_mock.MagicMock( get_credentials_for_presentation_request_by_referent=( async_mock.CoroutineMock() @@ -724,7 +724,7 @@ async def test_present_proof_credentials_double_ldp_vp_claim_format(self): async_mock.MagicMock(cred_value={"name": "Credential2"}), ] self.profile.context.injector.bind_instance( - IndyHolder, + AnonCredsHolder, async_mock.MagicMock( get_credentials_for_presentation_request_by_referent=( async_mock.CoroutineMock() @@ -829,7 +829,7 @@ async def test_present_proof_credentials_single_ldp_vp_error(self): ) self.profile.context.injector.bind_instance( - IndyHolder, + AnonCredsHolder, async_mock.MagicMock( get_credentials_for_presentation_request_by_referent=( async_mock.CoroutineMock() @@ -892,7 +892,7 @@ async def test_present_proof_credentials_double_ldp_vp_error(self): ) self.profile.context.injector.bind_instance( - IndyHolder, + AnonCredsHolder, async_mock.MagicMock( get_credentials_for_presentation_request_by_referent=( async_mock.CoroutineMock() @@ -952,7 +952,7 @@ async def test_present_proof_credentials_list_limit_disclosure_no_bbs(self): ) self.profile.context.injector.bind_instance( - IndyHolder, + AnonCredsHolder, async_mock.MagicMock( get_credentials_for_presentation_request_by_referent=( async_mock.CoroutineMock() @@ -1015,7 +1015,7 @@ async def test_present_proof_credentials_no_ldp_vp(self): ) self.profile.context.injector.bind_instance( - IndyHolder, + AnonCredsHolder, async_mock.MagicMock( get_credentials_for_presentation_request_by_referent=( async_mock.CoroutineMock() @@ -1080,7 +1080,7 @@ async def test_present_proof_credentials_list_schema_uri(self): async_mock.MagicMock(cred_value={"name": "Credential2"}), ] self.profile.context.injector.bind_instance( - IndyHolder, + AnonCredsHolder, async_mock.MagicMock( get_credentials_for_presentation_request_by_referent=( async_mock.CoroutineMock() @@ -1121,7 +1121,7 @@ async def test_present_proof_credentials_list_dif_error(self): self.request.query = {"extra_query": {}} self.profile.context.injector.bind_instance( - IndyHolder, + AnonCredsHolder, async_mock.MagicMock( get_credentials_for_presentation_request_by_referent=( async_mock.CoroutineMock() @@ -1499,7 +1499,7 @@ async def test_present_proof_send_bound_request(self): ), ) self.profile.context.injector.bind_instance( - IndyVerifier, + AnonCredsVerifier, async_mock.MagicMock( verify_presentation=async_mock.CoroutineMock(), ), @@ -1556,7 +1556,7 @@ async def test_present_proof_send_bound_request_not_found(self): ), ) self.profile.context.injector.bind_instance( - IndyVerifier, + AnonCredsVerifier, async_mock.MagicMock( verify_presentation=async_mock.CoroutineMock(), ), @@ -1596,7 +1596,7 @@ async def test_present_proof_send_bound_request_not_ready(self): ), ) self.profile.context.injector.bind_instance( - IndyVerifier, + AnonCredsVerifier, async_mock.MagicMock( verify_presentation=async_mock.CoroutineMock(), ), @@ -1653,7 +1653,7 @@ async def test_present_proof_send_bound_request_bad_state(self): ), ) self.profile.context.injector.bind_instance( - IndyVerifier, + AnonCredsVerifier, async_mock.MagicMock( verify_presentation=async_mock.CoroutineMock(), ), @@ -1688,7 +1688,7 @@ async def test_present_proof_send_bound_request_x(self): ), ) self.profile.context.injector.bind_instance( - IndyVerifier, + AnonCredsVerifier, async_mock.MagicMock( verify_presentation=async_mock.CoroutineMock(), ), @@ -1744,7 +1744,7 @@ async def test_present_proof_send_presentation(self): "pres_ex_id": "dummy", } self.profile.context.injector.bind_instance( - IndyVerifier, + AnonCredsVerifier, async_mock.MagicMock( verify_presentation=async_mock.CoroutineMock(), ), @@ -1799,7 +1799,7 @@ async def test_present_proof_send_presentation_dif(self): "pres_ex_id": "dummy", } self.profile.context.injector.bind_instance( - IndyVerifier, + AnonCredsVerifier, async_mock.MagicMock( verify_presentation=async_mock.CoroutineMock(), ), @@ -1878,7 +1878,7 @@ async def test_present_proof_send_presentation_dif_error(self): error_msg=None, ) self.profile.context.injector.bind_instance( - IndyVerifier, + AnonCredsVerifier, async_mock.MagicMock( verify_presentation=async_mock.CoroutineMock(), ), @@ -1953,7 +1953,7 @@ async def test_present_proof_send_presentation_not_found(self): "pres_ex_id": "dummy", } self.profile.context.injector.bind_instance( - IndyVerifier, + AnonCredsVerifier, async_mock.MagicMock( verify_presentation=async_mock.CoroutineMock(), ), @@ -1998,7 +1998,7 @@ async def test_present_proof_send_presentation_not_ready(self): "pres_ex_id": "dummy", } self.profile.context.injector.bind_instance( - IndyVerifier, + AnonCredsVerifier, async_mock.MagicMock( verify_presentation=async_mock.CoroutineMock(), ), @@ -2075,7 +2075,7 @@ async def test_present_proof_send_presentation_x(self): "pres_ex_id": "dummy", } self.profile.context.injector.bind_instance( - IndyVerifier, + AnonCredsVerifier, async_mock.MagicMock( verify_presentation=async_mock.CoroutineMock(), ), diff --git a/aries_cloudagent/revocation/models/issuer_rev_reg_record.py b/aries_cloudagent/revocation/models/issuer_rev_reg_record.py index 1e3d41a9da..29658d59bb 100644 --- a/aries_cloudagent/revocation/models/issuer_rev_reg_record.py +++ b/aries_cloudagent/revocation/models/issuer_rev_reg_record.py @@ -13,14 +13,14 @@ from marshmallow import fields, validate from ...core.profile import Profile, ProfileSession -from ...indy.issuer import IndyIssuer, IndyIssuerError -from ...indy.models.revocation import ( +from ...anoncreds.issuer import AnonCredsIssuer, AnonCredsIssuerError +from ...anoncreds.models.revocation import ( IndyRevRegDef, IndyRevRegDefSchema, IndyRevRegEntry, IndyRevRegEntrySchema, ) -from ...indy.util import indy_client_dir +from ...anoncreds.util import indy_client_dir from ...ledger.base import BaseLedger from ...ledger.error import LedgerError, LedgerTransactionError from ...messaging.models.base_record import BaseRecord, BaseRecordSchema @@ -181,7 +181,7 @@ async def generate_registry(self, profile: Profile): ) ) - issuer = profile.inject(IndyIssuer) + issuer = profile.inject(AnonCredsIssuer) tails_hopper_dir = indy_client_dir(join("tails", ".hopper"), create=True) LOGGER.debug("Creating revocation registry with size: %d", self.max_cred_num) @@ -199,7 +199,7 @@ async def generate_registry(self, profile: Profile): self.max_cred_num, tails_hopper_dir, ) - except IndyIssuerError as err: + except AnonCredsIssuerError as err: raise RevocationError() from err if self.revoc_reg_id and revoc_reg_id != self.revoc_reg_id: diff --git a/aries_cloudagent/revocation/models/revocation_registry.py b/aries_cloudagent/revocation/models/revocation_registry.py index cfb97eb467..4d081de2ff 100644 --- a/aries_cloudagent/revocation/models/revocation_registry.py +++ b/aries_cloudagent/revocation/models/revocation_registry.py @@ -10,7 +10,7 @@ from requests import Session from requests.exceptions import RequestException -from ...indy.util import indy_client_dir +from ...anoncreds.util import indy_client_dir from ..error import RevocationError import hashlib diff --git a/aries_cloudagent/revocation/models/tests/test_issuer_rev_reg_record.py b/aries_cloudagent/revocation/models/tests/test_issuer_rev_reg_record.py index 8154884aeb..5390b47dc5 100644 --- a/aries_cloudagent/revocation/models/tests/test_issuer_rev_reg_record.py +++ b/aries_cloudagent/revocation/models/tests/test_issuer_rev_reg_record.py @@ -5,9 +5,9 @@ from asynctest import TestCase as AsyncTestCase, mock as async_mock from ....core.in_memory import InMemoryProfile -from ....indy.issuer import IndyIssuer, IndyIssuerError -from ....indy.models.revocation import IndyRevRegDef -from ....indy.util import indy_client_dir +from ....anoncreds.issuer import AnonCredsIssuer, AnonCredsIssuerError +from ....anoncreds.models.revocation import IndyRevRegDef +from ....anoncreds.util import indy_client_dir from ....ledger.base import BaseLedger from ....tails.base import BaseTailsServer @@ -85,13 +85,13 @@ async def test_generate_registry_etc(self): cred_def_id=CRED_DEF_ID, revoc_reg_id=REV_REG_ID, ) - issuer = async_mock.MagicMock(IndyIssuer) - self.profile.context.injector.bind_instance(IndyIssuer, issuer) + issuer = async_mock.MagicMock(AnonCredsIssuer) + self.profile.context.injector.bind_instance(AnonCredsIssuer, issuer) with async_mock.patch.object( issuer, "create_and_store_revocation_registry", async_mock.CoroutineMock() ) as mock_create_store_rr: - mock_create_store_rr.side_effect = IndyIssuerError("Not this time") + mock_create_store_rr.side_effect = AnonCredsIssuerError("Not this time") with self.assertRaises(RevocationError): await rec.generate_registry(self.profile) diff --git a/aries_cloudagent/revocation/models/tests/test_revocation_registry.py b/aries_cloudagent/revocation/models/tests/test_revocation_registry.py index 5cc277ec60..34b3ec8416 100644 --- a/aries_cloudagent/revocation/models/tests/test_revocation_registry.py +++ b/aries_cloudagent/revocation/models/tests/test_revocation_registry.py @@ -9,7 +9,7 @@ import base58 -from ....indy.util import indy_client_dir +from ....anoncreds.util import indy_client_dir from ...error import RevocationError diff --git a/aries_cloudagent/revocation/tests/test_manager.py b/aries_cloudagent/revocation/tests/test_manager.py index ec026b9afd..5fe2c86bc7 100644 --- a/aries_cloudagent/revocation/tests/test_manager.py +++ b/aries_cloudagent/revocation/tests/test_manager.py @@ -8,7 +8,7 @@ ) from ...core.in_memory import InMemoryProfile -from ...indy.issuer import IndyIssuer +from ...anoncreds.issuer import AnonCredsIssuer from ...protocols.issue_credential.v1_0.models.credential_exchange import ( V10CredentialExchange, ) @@ -45,7 +45,7 @@ async def test_revoke_credential_publish(self): clear_pending=async_mock.CoroutineMock(), pending_pub=["2"], ) - issuer = async_mock.MagicMock(IndyIssuer, autospec=True) + issuer = async_mock.MagicMock(AnonCredsIssuer, autospec=True) issuer.revoke_credentials = async_mock.CoroutineMock( return_value=( json.dumps( @@ -61,7 +61,7 @@ async def test_revoke_credential_publish(self): [], ) ) - self.profile.context.injector.bind_instance(IndyIssuer, issuer) + self.profile.context.injector.bind_instance(AnonCredsIssuer, issuer) with async_mock.patch.object( test_module.IssuerCredRevRecord, @@ -105,8 +105,8 @@ async def test_revoke_cred_by_cxid_not_found(self): ) as mock_retrieve: mock_retrieve.side_effect = test_module.StorageNotFoundError("no such rec") - issuer = async_mock.MagicMock(IndyIssuer, autospec=True) - self.profile.context.injector.bind_instance(IndyIssuer, issuer) + issuer = async_mock.MagicMock(AnonCredsIssuer, autospec=True) + self.profile.context.injector.bind_instance(AnonCredsIssuer, issuer) with self.assertRaises(RevocationManagerError): await self.manager.revoke_credential_by_cred_ex_id(CRED_EX_ID) @@ -128,8 +128,8 @@ async def test_revoke_credential_no_rev_reg_rec(self): return_value=None ) - issuer = async_mock.MagicMock(IndyIssuer, autospec=True) - self.profile.context.injector.bind_instance(IndyIssuer, issuer) + issuer = async_mock.MagicMock(AnonCredsIssuer, autospec=True) + self.profile.context.injector.bind_instance(AnonCredsIssuer, issuer) with self.assertRaises(RevocationManagerError): await self.manager.revoke_credential(REV_REG_ID, CRED_REV_ID) @@ -139,8 +139,8 @@ async def test_revoke_credential_pend(self): mock_issuer_rev_reg_record = async_mock.MagicMock( mark_pending=async_mock.CoroutineMock() ) - issuer = async_mock.MagicMock(IndyIssuer, autospec=True) - self.profile.context.injector.bind_instance(IndyIssuer, issuer) + issuer = async_mock.MagicMock(AnonCredsIssuer, autospec=True) + self.profile.context.injector.bind_instance(AnonCredsIssuer, issuer) with async_mock.patch.object( test_module, "IndyRevocation", autospec=True @@ -200,7 +200,7 @@ async def test_publish_pending_revocations_basic(self): "retrieve_by_id", async_mock.CoroutineMock(return_value=mock_issuer_rev_reg_record), ): - issuer = async_mock.MagicMock(IndyIssuer, autospec=True) + issuer = async_mock.MagicMock(AnonCredsIssuer, autospec=True) issuer.merge_revocation_registry_deltas = async_mock.CoroutineMock( side_effect=deltas ) @@ -208,7 +208,7 @@ async def test_publish_pending_revocations_basic(self): issuer.revoke_credentials = async_mock.CoroutineMock( side_effect=[(json.dumps(delta), []) for delta in deltas] ) - self.profile.context.injector.bind_instance(IndyIssuer, issuer) + self.profile.context.injector.bind_instance(AnonCredsIssuer, issuer) result = await self.manager.publish_pending_revocations() assert result == {REV_REG_ID: ["1", "2"]} @@ -259,7 +259,7 @@ async def test_publish_pending_revocations_1_rev_reg_all(self): side_effect=lambda _, id, **args: mock_issuer_rev_reg_records[id] ), ): - issuer = async_mock.MagicMock(IndyIssuer, autospec=True) + issuer = async_mock.MagicMock(AnonCredsIssuer, autospec=True) issuer.merge_revocation_registry_deltas = async_mock.CoroutineMock( side_effect=deltas ) @@ -267,7 +267,7 @@ async def test_publish_pending_revocations_1_rev_reg_all(self): issuer.revoke_credentials = async_mock.CoroutineMock( side_effect=[(json.dumps(delta), []) for delta in deltas] ) - self.profile.context.injector.bind_instance(IndyIssuer, issuer) + self.profile.context.injector.bind_instance(AnonCredsIssuer, issuer) result = await self.manager.publish_pending_revocations({REV_REG_ID: None}) assert result == {REV_REG_ID: ["1", "2"]} @@ -319,7 +319,7 @@ async def test_publish_pending_revocations_1_rev_reg_some(self): side_effect=lambda _, id, **args: mock_issuer_rev_reg_records[id] ), ): - issuer = async_mock.MagicMock(IndyIssuer, autospec=True) + issuer = async_mock.MagicMock(AnonCredsIssuer, autospec=True) issuer.merge_revocation_registry_deltas = async_mock.CoroutineMock( side_effect=deltas ) @@ -327,7 +327,7 @@ async def test_publish_pending_revocations_1_rev_reg_some(self): issuer.revoke_credentials = async_mock.CoroutineMock( side_effect=[(json.dumps(delta), []) for delta in deltas] ) - self.profile.context.injector.bind_instance(IndyIssuer, issuer) + self.profile.context.injector.bind_instance(AnonCredsIssuer, issuer) result = await self.manager.publish_pending_revocations({REV_REG_ID: "2"}) assert result == {REV_REG_ID: ["2"]} diff --git a/aries_cloudagent/storage/tests/test_indy_storage.py b/aries_cloudagent/storage/tests/test_indy_storage.py index ff1092cf61..7b836571e5 100644 --- a/aries_cloudagent/storage/tests/test_indy_storage.py +++ b/aries_cloudagent/storage/tests/test_indy_storage.py @@ -12,7 +12,7 @@ from asynctest import mock as async_mock from ...config.injection_context import InjectionContext -from ...indy.sdk.profile import IndySdkProfileManager, IndySdkProfile +from ...anoncreds.sdk.profile import IndySdkProfileManager, IndySdkProfile from ...storage.base import BaseStorage from ...storage.error import StorageError, StorageSearchError from ...storage.indy import IndySdkStorage @@ -64,7 +64,7 @@ class TestIndySdkStorage(test_in_memory_storage.TestInMemoryStorage): @pytest.mark.asyncio async def test_record(self): with async_mock.patch( - "aries_cloudagent.indy.sdk.wallet_plugin.load_postgres_plugin", + "aries_cloudagent.anoncreds.sdk.wallet_plugin.load_postgres_plugin", async_mock.MagicMock(), ) as mock_load, async_mock.patch.object( indy.wallet, "create_wallet", async_mock.CoroutineMock() @@ -235,7 +235,7 @@ async def test_record(self): @pytest.mark.asyncio async def test_storage_search_x(self): with async_mock.patch( - "aries_cloudagent.indy.sdk.wallet_plugin.load_postgres_plugin", + "aries_cloudagent.anoncreds.sdk.wallet_plugin.load_postgres_plugin", async_mock.MagicMock(), ) as mock_load, async_mock.patch.object( indy.wallet, "create_wallet", async_mock.CoroutineMock() diff --git a/aries_cloudagent/storage/vc_holder/indy.py b/aries_cloudagent/storage/vc_holder/indy.py index 219317ee00..8362eb8134 100644 --- a/aries_cloudagent/storage/vc_holder/indy.py +++ b/aries_cloudagent/storage/vc_holder/indy.py @@ -4,7 +4,7 @@ from dateutil.parser import ParserError from typing import Mapping, Sequence -from ...indy.sdk.wallet_setup import IndyOpenWallet +from ...anoncreds.sdk.wallet_setup import IndyOpenWallet from ..indy import IndySdkStorage, IndySdkStorageSearch diff --git a/aries_cloudagent/storage/vc_holder/tests/test_indy_vc_holder.py b/aries_cloudagent/storage/vc_holder/tests/test_indy_vc_holder.py index fa092511b4..5990b297da 100644 --- a/aries_cloudagent/storage/vc_holder/tests/test_indy_vc_holder.py +++ b/aries_cloudagent/storage/vc_holder/tests/test_indy_vc_holder.py @@ -3,7 +3,7 @@ from ....config.injection_context import InjectionContext -from ....indy.sdk.profile import IndySdkProfileManager, IndySdkProfile +from ....anoncreds.sdk.profile import IndySdkProfileManager, IndySdkProfile from ....ledger.indy import IndySdkLedgerPool from ....wallet.indy import IndySdkWallet diff --git a/aries_cloudagent/wallet/tests/test_indy_wallet.py b/aries_cloudagent/wallet/tests/test_indy_wallet.py index 47ae72cdec..107e8c5656 100644 --- a/aries_cloudagent/wallet/tests/test_indy_wallet.py +++ b/aries_cloudagent/wallet/tests/test_indy_wallet.py @@ -12,9 +12,9 @@ from ...config.injection_context import InjectionContext from ...core.error import ProfileDuplicateError, ProfileError, ProfileNotFoundError from ...core.in_memory import InMemoryProfile -from ...indy.sdk import wallet_setup as test_setup_module -from ...indy.sdk.profile import IndySdkProfile, IndySdkProfileManager -from ...indy.sdk.wallet_setup import IndyWalletConfig +from ...anoncreds.sdk import wallet_setup as test_setup_module +from ...anoncreds.sdk.profile import IndySdkProfile, IndySdkProfileManager +from ...anoncreds.sdk.wallet_setup import IndyWalletConfig from ...ledger.endpoint_type import EndpointType from ...ledger.indy import IndySdkLedgerPool from ...wallet.did_method import SOV, DIDMethods diff --git a/docs/generated/aries_cloudagent.indy.credx.rst b/docs/generated/aries_cloudagent.indy.credx.rst index b80d4017f7..17237c9517 100644 --- a/docs/generated/aries_cloudagent.indy.credx.rst +++ b/docs/generated/aries_cloudagent.indy.credx.rst @@ -1,7 +1,7 @@ aries\_cloudagent.indy.credx package ==================================== -.. automodule:: aries_cloudagent.indy.credx +.. automodule:: aries_cloudagent.anoncreds.credx :members: :undoc-members: :show-inheritance: @@ -12,7 +12,7 @@ Submodules aries\_cloudagent.indy.credx.holder module ------------------------------------------ -.. automodule:: aries_cloudagent.indy.credx.holder +.. automodule:: aries_cloudagent.anoncreds.credx.holder :members: :undoc-members: :show-inheritance: @@ -20,7 +20,7 @@ aries\_cloudagent.indy.credx.holder module aries\_cloudagent.indy.credx.issuer module ------------------------------------------ -.. automodule:: aries_cloudagent.indy.credx.issuer +.. automodule:: aries_cloudagent.anoncreds.credx.issuer :members: :undoc-members: :show-inheritance: @@ -28,7 +28,7 @@ aries\_cloudagent.indy.credx.issuer module aries\_cloudagent.indy.credx.verifier module -------------------------------------------- -.. automodule:: aries_cloudagent.indy.credx.verifier +.. automodule:: aries_cloudagent.anoncreds.credx.verifier :members: :undoc-members: :show-inheritance: diff --git a/docs/generated/aries_cloudagent.indy.models.rst b/docs/generated/aries_cloudagent.indy.models.rst index ae04af6ddb..82b45240b5 100644 --- a/docs/generated/aries_cloudagent.indy.models.rst +++ b/docs/generated/aries_cloudagent.indy.models.rst @@ -1,7 +1,7 @@ aries\_cloudagent.indy.models package ===================================== -.. automodule:: aries_cloudagent.indy.models +.. automodule:: aries_cloudagent.anoncreds.models :members: :undoc-members: :show-inheritance: @@ -12,7 +12,7 @@ Submodules aries\_cloudagent.indy.models.cred module ----------------------------------------- -.. automodule:: aries_cloudagent.indy.models.cred +.. automodule:: aries_cloudagent.anoncreds.models.cred :members: :undoc-members: :show-inheritance: @@ -20,7 +20,7 @@ aries\_cloudagent.indy.models.cred module aries\_cloudagent.indy.models.cred\_abstract module --------------------------------------------------- -.. automodule:: aries_cloudagent.indy.models.cred_abstract +.. automodule:: aries_cloudagent.anoncreds.models.cred_abstract :members: :undoc-members: :show-inheritance: @@ -28,7 +28,7 @@ aries\_cloudagent.indy.models.cred\_abstract module aries\_cloudagent.indy.models.cred\_def module ---------------------------------------------- -.. automodule:: aries_cloudagent.indy.models.cred_def +.. automodule:: aries_cloudagent.anoncreds.models.cred_def :members: :undoc-members: :show-inheritance: @@ -36,7 +36,7 @@ aries\_cloudagent.indy.models.cred\_def module aries\_cloudagent.indy.models.cred\_precis module ------------------------------------------------- -.. automodule:: aries_cloudagent.indy.models.cred_precis +.. automodule:: aries_cloudagent.anoncreds.models.cred_precis :members: :undoc-members: :show-inheritance: @@ -44,7 +44,7 @@ aries\_cloudagent.indy.models.cred\_precis module aries\_cloudagent.indy.models.cred\_request module -------------------------------------------------- -.. automodule:: aries_cloudagent.indy.models.cred_request +.. automodule:: aries_cloudagent.anoncreds.models.cred_request :members: :undoc-members: :show-inheritance: @@ -52,7 +52,7 @@ aries\_cloudagent.indy.models.cred\_request module aries\_cloudagent.indy.models.non\_rev\_interval module ------------------------------------------------------- -.. automodule:: aries_cloudagent.indy.models.non_rev_interval +.. automodule:: aries_cloudagent.anoncreds.models.non_rev_interval :members: :undoc-members: :show-inheritance: @@ -60,7 +60,7 @@ aries\_cloudagent.indy.models.non\_rev\_interval module aries\_cloudagent.indy.models.predicate module ---------------------------------------------- -.. automodule:: aries_cloudagent.indy.models.predicate +.. automodule:: aries_cloudagent.anoncreds.models.predicate :members: :undoc-members: :show-inheritance: @@ -68,7 +68,7 @@ aries\_cloudagent.indy.models.predicate module aries\_cloudagent.indy.models.pres\_preview module -------------------------------------------------- -.. automodule:: aries_cloudagent.indy.models.pres_preview +.. automodule:: aries_cloudagent.anoncreds.models.pres_preview :members: :undoc-members: :show-inheritance: @@ -76,7 +76,7 @@ aries\_cloudagent.indy.models.pres\_preview module aries\_cloudagent.indy.models.proof module ------------------------------------------ -.. automodule:: aries_cloudagent.indy.models.proof +.. automodule:: aries_cloudagent.anoncreds.models.proof :members: :undoc-members: :show-inheritance: @@ -84,7 +84,7 @@ aries\_cloudagent.indy.models.proof module aries\_cloudagent.indy.models.proof\_request module --------------------------------------------------- -.. automodule:: aries_cloudagent.indy.models.proof_request +.. automodule:: aries_cloudagent.anoncreds.models.proof_request :members: :undoc-members: :show-inheritance: @@ -92,7 +92,7 @@ aries\_cloudagent.indy.models.proof\_request module aries\_cloudagent.indy.models.requested\_creds module ----------------------------------------------------- -.. automodule:: aries_cloudagent.indy.models.requested_creds +.. automodule:: aries_cloudagent.anoncreds.models.requested_creds :members: :undoc-members: :show-inheritance: @@ -100,7 +100,7 @@ aries\_cloudagent.indy.models.requested\_creds module aries\_cloudagent.indy.models.revocation module ----------------------------------------------- -.. automodule:: aries_cloudagent.indy.models.revocation +.. automodule:: aries_cloudagent.anoncreds.models.revocation :members: :undoc-members: :show-inheritance: @@ -108,7 +108,7 @@ aries\_cloudagent.indy.models.revocation module aries\_cloudagent.indy.models.schema module ------------------------------------------- -.. automodule:: aries_cloudagent.indy.models.schema +.. automodule:: aries_cloudagent.anoncreds.models.schema :members: :undoc-members: :show-inheritance: @@ -116,7 +116,7 @@ aries\_cloudagent.indy.models.schema module aries\_cloudagent.indy.models.xform module ------------------------------------------ -.. automodule:: aries_cloudagent.indy.models.xform +.. automodule:: aries_cloudagent.anoncreds.models.xform :members: :undoc-members: :show-inheritance: diff --git a/docs/generated/aries_cloudagent.indy.rst b/docs/generated/aries_cloudagent.indy.rst index 0e5c50f637..b4079c2f93 100644 --- a/docs/generated/aries_cloudagent.indy.rst +++ b/docs/generated/aries_cloudagent.indy.rst @@ -1,7 +1,7 @@ aries\_cloudagent.indy package ============================== -.. automodule:: aries_cloudagent.indy +.. automodule:: aries_cloudagent.anoncreds :members: :undoc-members: :show-inheritance: @@ -12,9 +12,9 @@ Subpackages .. toctree:: :maxdepth: 4 - aries_cloudagent.indy.credx - aries_cloudagent.indy.models - aries_cloudagent.indy.sdk + aries_cloudagent.anoncreds.credx + aries_cloudagent.anoncreds.models + aries_cloudagent.anoncreds.sdk Submodules ---------- @@ -22,7 +22,7 @@ Submodules aries\_cloudagent.indy.holder module ------------------------------------ -.. automodule:: aries_cloudagent.indy.holder +.. automodule:: aries_cloudagent.anoncreds.holder :members: :undoc-members: :show-inheritance: @@ -30,7 +30,7 @@ aries\_cloudagent.indy.holder module aries\_cloudagent.indy.issuer module ------------------------------------ -.. automodule:: aries_cloudagent.indy.issuer +.. automodule:: aries_cloudagent.anoncreds.issuer :members: :undoc-members: :show-inheritance: @@ -38,7 +38,7 @@ aries\_cloudagent.indy.issuer module aries\_cloudagent.indy.util module ---------------------------------- -.. automodule:: aries_cloudagent.indy.util +.. automodule:: aries_cloudagent.anoncreds.util :members: :undoc-members: :show-inheritance: @@ -46,7 +46,7 @@ aries\_cloudagent.indy.util module aries\_cloudagent.indy.verifier module -------------------------------------- -.. automodule:: aries_cloudagent.indy.verifier +.. automodule:: aries_cloudagent.anoncreds.verifier :members: :undoc-members: :show-inheritance: diff --git a/docs/generated/aries_cloudagent.indy.sdk.rst b/docs/generated/aries_cloudagent.indy.sdk.rst index be5adbf5ae..1bb941f388 100644 --- a/docs/generated/aries_cloudagent.indy.sdk.rst +++ b/docs/generated/aries_cloudagent.indy.sdk.rst @@ -1,7 +1,7 @@ aries\_cloudagent.indy.sdk package ================================== -.. automodule:: aries_cloudagent.indy.sdk +.. automodule:: aries_cloudagent.anoncreds.sdk :members: :undoc-members: :show-inheritance: @@ -12,7 +12,7 @@ Submodules aries\_cloudagent.indy.sdk.error module --------------------------------------- -.. automodule:: aries_cloudagent.indy.sdk.error +.. automodule:: aries_cloudagent.anoncreds.sdk.error :members: :undoc-members: :show-inheritance: @@ -20,7 +20,7 @@ aries\_cloudagent.indy.sdk.error module aries\_cloudagent.indy.sdk.holder module ---------------------------------------- -.. automodule:: aries_cloudagent.indy.sdk.holder +.. automodule:: aries_cloudagent.anoncreds.sdk.holder :members: :undoc-members: :show-inheritance: @@ -28,7 +28,7 @@ aries\_cloudagent.indy.sdk.holder module aries\_cloudagent.indy.sdk.issuer module ---------------------------------------- -.. automodule:: aries_cloudagent.indy.sdk.issuer +.. automodule:: aries_cloudagent.anoncreds.sdk.issuer :members: :undoc-members: :show-inheritance: @@ -36,7 +36,7 @@ aries\_cloudagent.indy.sdk.issuer module aries\_cloudagent.indy.sdk.profile module ----------------------------------------- -.. automodule:: aries_cloudagent.indy.sdk.profile +.. automodule:: aries_cloudagent.anoncreds.sdk.profile :members: :undoc-members: :show-inheritance: @@ -44,7 +44,7 @@ aries\_cloudagent.indy.sdk.profile module aries\_cloudagent.indy.sdk.util module -------------------------------------- -.. automodule:: aries_cloudagent.indy.sdk.util +.. automodule:: aries_cloudagent.anoncreds.sdk.util :members: :undoc-members: :show-inheritance: @@ -52,7 +52,7 @@ aries\_cloudagent.indy.sdk.util module aries\_cloudagent.indy.sdk.verifier module ------------------------------------------ -.. automodule:: aries_cloudagent.indy.sdk.verifier +.. automodule:: aries_cloudagent.anoncreds.sdk.verifier :members: :undoc-members: :show-inheritance: @@ -60,7 +60,7 @@ aries\_cloudagent.indy.sdk.verifier module aries\_cloudagent.indy.sdk.wallet\_plugin module ------------------------------------------------ -.. automodule:: aries_cloudagent.indy.sdk.wallet_plugin +.. automodule:: aries_cloudagent.anoncreds.sdk.wallet_plugin :members: :undoc-members: :show-inheritance: @@ -68,7 +68,7 @@ aries\_cloudagent.indy.sdk.wallet\_plugin module aries\_cloudagent.indy.sdk.wallet\_setup module ----------------------------------------------- -.. automodule:: aries_cloudagent.indy.sdk.wallet_setup +.. automodule:: aries_cloudagent.anoncreds.sdk.wallet_setup :members: :undoc-members: :show-inheritance: diff --git a/docs/generated/aries_cloudagent.rst b/docs/generated/aries_cloudagent.rst index ef01b45059..6891febae2 100644 --- a/docs/generated/aries_cloudagent.rst +++ b/docs/generated/aries_cloudagent.rst @@ -21,7 +21,7 @@ Subpackages aries_cloudagent.core aries_cloudagent.did aries_cloudagent.holder - aries_cloudagent.indy + aries_cloudagent.anoncreds aries_cloudagent.ledger aries_cloudagent.messaging aries_cloudagent.multitenant From df66bc9f467daf62ece688dcf06bb61fd4b59c05 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Wed, 22 Feb 2023 14:59:03 -0700 Subject: [PATCH 007/150] fix: add requirements.anoncreds.txt to Dockerfile.indy Signed-off-by: Char Howland --- docker/Dockerfile.indy | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docker/Dockerfile.indy b/docker/Dockerfile.indy index edd7200b8a..5c9e936991 100644 --- a/docker/Dockerfile.indy +++ b/docker/Dockerfile.indy @@ -195,7 +195,8 @@ RUN pip3 install --no-cache-dir \ -r requirements.txt \ -r requirements.askar.txt \ -r requirements.bbs.txt \ - -r requirements.dev.txt + -r requirements.dev.txt \ + -r requirements.anoncreds.txt ADD --chown=indy:root . . USER indy From d7d895fc9cbcbb76787f23339db33f7c2c63d151 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Thu, 23 Feb 2023 16:12:46 -0700 Subject: [PATCH 008/150] fix: mount libanoncreds.so in volume Signed-off-by: Char Howland --- docker/Dockerfile.test | 4 +++- scripts/run_tests | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/docker/Dockerfile.test b/docker/Dockerfile.test index 6a1b4df76a..a4eac06007 100644 --- a/docker/Dockerfile.test +++ b/docker/Dockerfile.test @@ -3,6 +3,7 @@ FROM python:${python_version}-slim-buster RUN apt-get update -y && \ apt-get install -y --no-install-recommends \ + git \ libsodium23 && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* @@ -15,7 +16,8 @@ RUN pip3 install --no-cache-dir \ -r requirements.txt \ -r requirements.askar.txt \ -r requirements.bbs.txt \ - -r requirements.dev.txt + -r requirements.dev.txt \ + -r requirements.anoncreds.txt ADD . . diff --git a/scripts/run_tests b/scripts/run_tests index 898d2361dc..508fa4c56e 100755 --- a/scripts/run_tests +++ b/scripts/run_tests @@ -42,4 +42,5 @@ fi $CONTAINER_RUNTIME run --rm -ti --name aries-cloudagent-runner \ --platform linux/amd64 \ -v "$(pwd)/../test-reports:/usr/src/app/test-reports:z" \ + -v "$HOME/aries-cloudagent-python/libanoncreds.so:/usr/local/lib/python3.6/site-packages/anoncreds/libanoncreds.so" \ $DOCKER_ARGS aries-cloudagent-test "$@" From ede5803c06ea4c027c67d400431ad60fd312e5c5 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Thu, 23 Feb 2023 18:23:47 -0500 Subject: [PATCH 009/150] fix: pull binaries from a temporary location Signed-off-by: Daniel Bluhm --- docker/Dockerfile.indy | 3 ++- docker/Dockerfile.test | 18 +++++++++--------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/docker/Dockerfile.indy b/docker/Dockerfile.indy index 5c9e936991..6b2b32ca53 100644 --- a/docker/Dockerfile.indy +++ b/docker/Dockerfile.indy @@ -196,7 +196,8 @@ RUN pip3 install --no-cache-dir \ -r requirements.askar.txt \ -r requirements.bbs.txt \ -r requirements.dev.txt \ - -r requirements.anoncreds.txt + -r requirements.anoncreds.txt +RUN curl -sL https://github.com/Indicio-tech/anoncreds-rs/releases/download/v0.1.0-dev.9/library-linux-x86_64.tar.gz | tar -xz -C $HOME/.local/lib ADD --chown=indy:root . . USER indy diff --git a/docker/Dockerfile.test b/docker/Dockerfile.test index a4eac06007..84417e159e 100644 --- a/docker/Dockerfile.test +++ b/docker/Dockerfile.test @@ -2,22 +2,22 @@ ARG python_version=3.6.13 FROM python:${python_version}-slim-buster RUN apt-get update -y && \ - apt-get install -y --no-install-recommends \ - git \ - libsodium23 && \ + apt-get install -y --no-install-recommends \ + libsodium23 git curl && \ apt-get clean && \ - rm -rf /var/lib/apt/lists/* + rm -rf /var/lib/apt/lists/* WORKDIR /usr/src/app ADD requirements*.txt ./ RUN pip3 install --no-cache-dir \ - -r requirements.txt \ - -r requirements.askar.txt \ - -r requirements.bbs.txt \ - -r requirements.dev.txt \ - -r requirements.anoncreds.txt + -r requirements.txt \ + -r requirements.askar.txt \ + -r requirements.bbs.txt \ + -r requirements.dev.txt \ + -r requirements.anoncreds.txt +RUN curl -sL https://github.com/Indicio-tech/anoncreds-rs/releases/download/v0.1.0-dev.9/library-linux-x86_64.tar.gz | tar -xz -C /usr/local/lib/python3.6/site-packages/anoncreds/ ADD . . From f8c7ca261aa6f0c568c9f8f93596ebc82dc2031b Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Thu, 23 Feb 2023 19:54:50 -0500 Subject: [PATCH 010/150] fix: add anoncreds deps to gha Signed-off-by: Daniel Bluhm --- .github/workflows/tests.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 919d1e4f21..c9b5dc8077 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -29,7 +29,8 @@ jobs: -r requirements.txt \ -r requirements.askar.txt \ -r requirements.bbs.txt \ - -r requirements.dev.txt + -r requirements.dev.txt \ + -r requirements.anoncreds.txt - name: Tests run: | pytest From 60973f47303ca71f348268f1610615115445425d Mon Sep 17 00:00:00 2001 From: Adam Burdett Date: Fri, 24 Feb 2023 09:28:45 -0700 Subject: [PATCH 011/150] removed volume from run test script Signed-off-by: Adam Burdett --- scripts/run_tests | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/run_tests b/scripts/run_tests index 508fa4c56e..898d2361dc 100755 --- a/scripts/run_tests +++ b/scripts/run_tests @@ -42,5 +42,4 @@ fi $CONTAINER_RUNTIME run --rm -ti --name aries-cloudagent-runner \ --platform linux/amd64 \ -v "$(pwd)/../test-reports:/usr/src/app/test-reports:z" \ - -v "$HOME/aries-cloudagent-python/libanoncreds.so:/usr/local/lib/python3.6/site-packages/anoncreds/libanoncreds.so" \ $DOCKER_ARGS aries-cloudagent-test "$@" From 49a66f49d55b96b7c71ebe924451f3647b459273 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Fri, 24 Feb 2023 11:37:18 -0500 Subject: [PATCH 012/150] fix: library path for anoncreds bin Signed-off-by: Daniel Bluhm --- docker/Dockerfile.indy | 2 +- docker/Dockerfile.test | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/Dockerfile.indy b/docker/Dockerfile.indy index 6b2b32ca53..098c540d27 100644 --- a/docker/Dockerfile.indy +++ b/docker/Dockerfile.indy @@ -197,7 +197,7 @@ RUN pip3 install --no-cache-dir \ -r requirements.bbs.txt \ -r requirements.dev.txt \ -r requirements.anoncreds.txt -RUN curl -sL https://github.com/Indicio-tech/anoncreds-rs/releases/download/v0.1.0-dev.9/library-linux-x86_64.tar.gz | tar -xz -C $HOME/.local/lib +RUN curl -sL https://github.com/Indicio-tech/anoncreds-rs/releases/download/v0.1.0-dev.9/library-linux-x86_64.tar.gz | tar -xz -C /usr/local/lib/python3.9/site-packages/anoncreds/ ADD --chown=indy:root . . USER indy diff --git a/docker/Dockerfile.test b/docker/Dockerfile.test index 84417e159e..9c412eeecd 100644 --- a/docker/Dockerfile.test +++ b/docker/Dockerfile.test @@ -1,4 +1,4 @@ -ARG python_version=3.6.13 +ARG python_version=3.9.16 FROM python:${python_version}-slim-buster RUN apt-get update -y && \ From dd7268b61d5d45caa687e0254ad2830a3ff45734 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Fri, 24 Feb 2023 11:45:33 -0500 Subject: [PATCH 013/150] fix: library path for test dockerfile for anoncreds bin Signed-off-by: Daniel Bluhm --- docker/Dockerfile.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Dockerfile.test b/docker/Dockerfile.test index 9c412eeecd..d3764af32a 100644 --- a/docker/Dockerfile.test +++ b/docker/Dockerfile.test @@ -17,7 +17,7 @@ RUN pip3 install --no-cache-dir \ -r requirements.bbs.txt \ -r requirements.dev.txt \ -r requirements.anoncreds.txt -RUN curl -sL https://github.com/Indicio-tech/anoncreds-rs/releases/download/v0.1.0-dev.9/library-linux-x86_64.tar.gz | tar -xz -C /usr/local/lib/python3.6/site-packages/anoncreds/ +RUN curl -sL https://github.com/Indicio-tech/anoncreds-rs/releases/download/v0.1.0-dev.9/library-linux-x86_64.tar.gz | tar -xz -C /usr/local/lib/python3.9/site-packages/anoncreds/ ADD . . From d9f95ccb18762792ccaa75b06f3a7e5dd9c546d9 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Fri, 24 Feb 2023 14:02:18 -0700 Subject: [PATCH 014/150] fix: redefine schema id Signed-off-by: Char Howland --- aries_cloudagent/anoncreds/anoncreds/issuer.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/aries_cloudagent/anoncreds/anoncreds/issuer.py b/aries_cloudagent/anoncreds/anoncreds/issuer.py index ed979dd0b7..0c6796f7db 100644 --- a/aries_cloudagent/anoncreds/anoncreds/issuer.py +++ b/aries_cloudagent/anoncreds/anoncreds/issuer.py @@ -82,9 +82,9 @@ async def create_schema( """ try: schema = Schema.create( - origin_did, schema_name, schema_version, attribute_names + schema_name, schema_version, origin_did, attribute_names ) - schema_id = schema.id + schema_id = f"{origin_did}:2:{schema_name}:{schema_version}" schema_json = schema.to_json() async with self._profile.session() as session: await session.handle.insert(CATEGORY_SCHEMA, schema_id, schema_json) @@ -145,10 +145,11 @@ async def create_and_store_credential_definition( ) = await asyncio.get_event_loop().run_in_executor( None, lambda: CredentialDefinition.create( - origin_did, + f"{origin_did}:2:{schema['name']}:{schema['version']}", schema, - signature_type or DEFAULT_SIGNATURE_TYPE, + origin_did, tag or DEFAULT_CRED_DEF_TAG, + signature_type or DEFAULT_SIGNATURE_TYPE, support_revocation=support_revocation, ), ) From 2621224b64ab7c1480142c926550cd0343b7883a Mon Sep 17 00:00:00 2001 From: Adam Burdett Date: Fri, 24 Feb 2023 16:38:14 -0700 Subject: [PATCH 015/150] start of anoncreds admin api Signed-off-by: Adam Burdett --- .../anoncreds/anoncreds/routes.py | 36 +++++++++++++++++++ .../anoncreds/anoncreds/tests/test_routes.py | 16 +++++++++ aries_cloudagent/config/default_context.py | 1 + 3 files changed, 53 insertions(+) create mode 100644 aries_cloudagent/anoncreds/anoncreds/routes.py create mode 100644 aries_cloudagent/anoncreds/anoncreds/tests/test_routes.py diff --git a/aries_cloudagent/anoncreds/anoncreds/routes.py b/aries_cloudagent/anoncreds/anoncreds/routes.py new file mode 100644 index 0000000000..45493d0afa --- /dev/null +++ b/aries_cloudagent/anoncreds/anoncreds/routes.py @@ -0,0 +1,36 @@ +""" Anoncreds admin routes """ +# import json + +from aiohttp import web +# from aiohttp_apispec import (docs, match_info_schema, querystring_schema, +# request_schema, response_schema) + +SPEC_URI = "" + + +async def register(app: web.Application): + """Register routes.""" + + app.add_routes([ + web.post("/anoncreds/schemas", , allow_head=False), + web.get("/anoncreds/schemas/{schema_id}", , allow_head=False), + web.get("/anoncreds/schemas/created", ,allow_head=False), + web.post("/anoncreds/credential-definitions", ,allow_head=False), + web.get("/anoncreds/credential-definitions/{credential_definition_id}", ,allow_head=False), + web.get("/anoncreds/credential-definitions/created", ,allow_head=False), + ]) + + +def post_process_routes(app: web.Application): + """Amend swagger API.""" + + # Add top-level tags description + if "tags" not in app._state["swagger_dict"]: + app._state["swagger_dict"]["tags"] = [] + app._state["swagger_dict"]["tags"].append( + { + "name": "anoncreds", + "description": "Anoncreds management", + "externalDocs": {"description": "Specification", "url": SPEC_URI}, + } + ) diff --git a/aries_cloudagent/anoncreds/anoncreds/tests/test_routes.py b/aries_cloudagent/anoncreds/anoncreds/tests/test_routes.py new file mode 100644 index 0000000000..a4416071fb --- /dev/null +++ b/aries_cloudagent/anoncreds/anoncreds/tests/test_routes.py @@ -0,0 +1,16 @@ +from asynctest import mock as async_mock, TestCase as AsyncTestCase +from .. import routes as test_module + + +class TestAnoncredsRoutes(AsyncTestCase): + async def test_register(self): + mock_app = async_mock.MagicMock() + mock_app.add_routes = async_mock.MagicMock() + + await test_module.register(mock_app) + mock_app.add_routes.assert_called_once() + + async def test_post_process_routes(self): + mock_app = async_mock.MagicMock(_state={"swagger_dict": {}}) + test_module.post_process_routes(mock_app) + assert "tags" in mock_app._state["swagger_dict"] diff --git a/aries_cloudagent/config/default_context.py b/aries_cloudagent/config/default_context.py index 360f103fa1..44ee5c77c9 100644 --- a/aries_cloudagent/config/default_context.py +++ b/aries_cloudagent/config/default_context.py @@ -127,6 +127,7 @@ async def load_plugins(self, context: InjectionContext): plugin_registry.register_plugin("aries_cloudagent.revocation") plugin_registry.register_plugin("aries_cloudagent.resolver") plugin_registry.register_plugin("aries_cloudagent.wallet") + plugin_registry.register_plugin("aries_cloudagent.anoncreds.anoncreds") if context.settings.get("multitenant.admin_enabled"): plugin_registry.register_plugin("aries_cloudagent.multitenant.admin") From 2e68381e798c3fb768f19e6de2aa7284c42e5cd6 Mon Sep 17 00:00:00 2001 From: Adam Burdett Date: Mon, 27 Feb 2023 15:55:49 -0700 Subject: [PATCH 016/150] empty methods for anoncreds admin routes Signed-off-by: Adam Burdett --- .../anoncreds/anoncreds/routes.py | 69 ++++++++++++++++--- 1 file changed, 61 insertions(+), 8 deletions(-) diff --git a/aries_cloudagent/anoncreds/anoncreds/routes.py b/aries_cloudagent/anoncreds/anoncreds/routes.py index 45493d0afa..1942b13e86 100644 --- a/aries_cloudagent/anoncreds/anoncreds/routes.py +++ b/aries_cloudagent/anoncreds/anoncreds/routes.py @@ -2,23 +2,76 @@ # import json from aiohttp import web + # from aiohttp_apispec import (docs, match_info_schema, querystring_schema, # request_schema, response_schema) SPEC_URI = "" +async def schemas_post(request: web.BaseRequest): + raise NotImplementedError() + + +async def schema_get(request: web.BaseRequest): + raise NotImplementedError() + + +async def schemas_get(request: web.BaseRequest): + raise NotImplementedError() + + +async def schemas_get_created(request: web.BaseRequest): + raise NotImplementedError() + + +async def cred_def_post(request: web.BaseRequest): + raise NotImplementedError() + + +async def cred_def_get(request: web.BaseRequest): + raise NotImplementedError() + + +async def cred_defs_get(request: web.BaseRequest): + raise NotImplementedError() + + async def register(app: web.Application): """Register routes.""" - app.add_routes([ - web.post("/anoncreds/schemas", , allow_head=False), - web.get("/anoncreds/schemas/{schema_id}", , allow_head=False), - web.get("/anoncreds/schemas/created", ,allow_head=False), - web.post("/anoncreds/credential-definitions", ,allow_head=False), - web.get("/anoncreds/credential-definitions/{credential_definition_id}", ,allow_head=False), - web.get("/anoncreds/credential-definitions/created", ,allow_head=False), - ]) + app.add_routes( + [ + web.post("/anoncreds/schema", schemas_post, allow_head=False), + web.get("/anoncreds/schema/{schema_id}", schema_get, allow_head=False), + web.get( + "/anoncreds/schemas/issuer/{issuer_id}", schemas_get, allow_head=False + ), + web.get( + "/anoncreds/schemas/did-method/{did_method}", + schemas_get, + allow_head=False, + ), + web.post( + "/anoncreds/credential-definition", cred_def_post, allow_head=False + ), + web.get( + "/anoncreds/credential-definition/{credential_definition_id}", + cred_def_get, + allow_head=False, + ), + web.get( + "/anoncreds/credential-definitions/issuer/{did_method}", + cred_defs_get, + allow_head=False, + ), + web.get( + "/anoncreds/credential-definitions/did-method/{did_method}", + cred_defs_get, + allow_head=False, + ), + ] + ) def post_process_routes(app: web.Application): From e21cdce6cd78b2f44c8fe4c3bdf581bc0bfab329 Mon Sep 17 00:00:00 2001 From: Adam Burdett Date: Wed, 1 Mar 2023 12:43:06 -0700 Subject: [PATCH 017/150] start of openapi schemas Signed-off-by: Adam Burdett --- .../anoncreds/anoncreds/routes.py | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/aries_cloudagent/anoncreds/anoncreds/routes.py b/aries_cloudagent/anoncreds/anoncreds/routes.py index 1942b13e86..9cf295139c 100644 --- a/aries_cloudagent/anoncreds/anoncreds/routes.py +++ b/aries_cloudagent/anoncreds/anoncreds/routes.py @@ -2,37 +2,54 @@ # import json from aiohttp import web - -# from aiohttp_apispec import (docs, match_info_schema, querystring_schema, -# request_schema, response_schema) +from aiohttp_apispec import (docs, # request_schema, response_schema + match_info_schema, querystring_schema) +from marshmallow import fields +from ...messaging.valid import ( UUIDFour +) +from ...messaging.models.openapi import OpenAPISchema SPEC_URI = "" +class SchemaIdMatchInfoSchema(OpenAPISchema): + """""" + schema_id = fields.Str( + description="Schema identifier", required=True, example=UUIDFour.EXAMPLE + ) + +@docs(tags=["anoncreds"], summary="") async def schemas_post(request: web.BaseRequest): raise NotImplementedError() +@docs(tags=["anoncreds"], summary="") +@match_info_schema(HolderCredIdMatchInfoSchema()) async def schema_get(request: web.BaseRequest): raise NotImplementedError() +@docs(tags=["anoncreds"], summary="") async def schemas_get(request: web.BaseRequest): raise NotImplementedError() +@docs(tags=["anoncreds"], summary="") async def schemas_get_created(request: web.BaseRequest): raise NotImplementedError() +@docs(tags=["anoncreds"], summary="") async def cred_def_post(request: web.BaseRequest): raise NotImplementedError() +@docs(tags=["anoncreds"], summary="") async def cred_def_get(request: web.BaseRequest): raise NotImplementedError() +@docs(tags=["anoncreds"], summary="") async def cred_defs_get(request: web.BaseRequest): raise NotImplementedError() From be553ccbd9e716f30be53021e7b5e3a04df01ef4 Mon Sep 17 00:00:00 2001 From: Adam Burdett Date: Wed, 1 Mar 2023 13:32:47 -0700 Subject: [PATCH 018/150] use anoncreds issuer to create id Signed-off-by: Adam Burdett --- aries_cloudagent/anoncreds/anoncreds/issuer.py | 14 ++++++++++---- aries_cloudagent/anoncreds/issuer.py | 6 ++++-- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/aries_cloudagent/anoncreds/anoncreds/issuer.py b/aries_cloudagent/anoncreds/anoncreds/issuer.py index 0c6796f7db..6d0f7331c0 100644 --- a/aries_cloudagent/anoncreds/anoncreds/issuer.py +++ b/aries_cloudagent/anoncreds/anoncreds/issuer.py @@ -18,6 +18,7 @@ RevocationRegistryDelta, Schema, ) +from ..issuer import AnonCredsIssuer from ...askar.profile import AskarProfile @@ -137,6 +138,12 @@ async def create_and_store_credential_definition( A tuple of the credential definition ID and JSON """ + schema_id = AnonCredsIssuer.make_schema_id( + origin_did, schema["name"], schema["version"] + ) + cred_def_id = AnonCredsIssuer.make_credential_definition_id( + origin_did, schema, signature_type, tag + ) try: ( cred_def, @@ -145,7 +152,7 @@ async def create_and_store_credential_definition( ) = await asyncio.get_event_loop().run_in_executor( None, lambda: CredentialDefinition.create( - f"{origin_did}:2:{schema['name']}:{schema['version']}", + schema_id, schema, origin_did, tag or DEFAULT_CRED_DEF_TAG, @@ -153,7 +160,6 @@ async def create_and_store_credential_definition( support_revocation=support_revocation, ), ) - cred_def_id = cred_def.id cred_def_json = cred_def.to_json() except AnoncredsError as err: raise AnonCredsIssuerError("Error creating credential definition") from err @@ -164,7 +170,7 @@ async def create_and_store_credential_definition( cred_def_id, cred_def_json, # Note: Indy-SDK uses a separate SchemaId record for this - tags={"schema_id": schema["id"]}, + tags={"schema_id": schema_id}, ) await txn.handle.insert( CATEGORY_CRED_DEF_PRIVATE, @@ -606,7 +612,7 @@ async def create_and_store_revocation_registry( tails_dir_path=tails_base_path, ), ) - except AnonCredsError as err: + except AnoncredsError as err: raise AnonCredsIssuerError("Error creating revocation registry") from err rev_reg_def_id = rev_reg_def.id diff --git a/aries_cloudagent/anoncreds/issuer.py b/aries_cloudagent/anoncreds/issuer.py index e3669f451d..e4fbd7f687 100644 --- a/aries_cloudagent/anoncreds/issuer.py +++ b/aries_cloudagent/anoncreds/issuer.py @@ -31,8 +31,9 @@ def __repr__(self) -> str: """ return "<{}>".format(self.__class__.__name__) + @staticmethod def make_schema_id( - self, origin_did: str, schema_name: str, schema_version: str + origin_did: str, schema_name: str, schema_version: str ) -> str: """Derive the ID for a schema.""" return f"{origin_did}:2:{schema_name}:{schema_version}" @@ -59,8 +60,9 @@ async def create_schema( """ + @staticmethod def make_credential_definition_id( - self, origin_did: str, schema: dict, signature_type: str = None, tag: str = None + origin_did: str, schema: dict, signature_type: str = None, tag: str = None ) -> str: """Derive the ID for a credential definition.""" signature_type = signature_type or DEFAULT_SIGNATURE_TYPE From 3d62cc6ea96aecc07d5f9db7e1aaf43686cef459 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Thu, 2 Mar 2023 13:25:59 -0700 Subject: [PATCH 019/150] fix: pass in cred def id in CredentialOffer and RevocationRegistryDefinition methods Signed-off-by: Char Howland --- aries_cloudagent/anoncreds/anoncreds/issuer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aries_cloudagent/anoncreds/anoncreds/issuer.py b/aries_cloudagent/anoncreds/anoncreds/issuer.py index 6d0f7331c0..782c08cb8e 100644 --- a/aries_cloudagent/anoncreds/anoncreds/issuer.py +++ b/aries_cloudagent/anoncreds/anoncreds/issuer.py @@ -220,7 +220,7 @@ async def create_credential_offer(self, credential_definition_id: str) -> str: credential_offer = CredentialOffer.create( schema_id or cred_def.schema_id, - cred_def, + credential_definition_id, key_proof.raw_value, ) except AnoncredsError as err: @@ -604,7 +604,7 @@ async def create_and_store_revocation_registry( ) = await asyncio.get_event_loop().run_in_executor( None, lambda: RevocationRegistryDefinition.create( - origin_did, + cred_def_id, cred_def.raw_value, tag, revoc_def_type, From bdd3752cdcef77d49a5083e0d5f348407cbd1978 Mon Sep 17 00:00:00 2001 From: Adam Burdett Date: Fri, 3 Mar 2023 15:07:20 -0700 Subject: [PATCH 020/150] schema route details Signed-off-by: Adam Burdett --- .../anoncreds/anoncreds/routes.py | 132 ++++++++++++++---- 1 file changed, 106 insertions(+), 26 deletions(-) diff --git a/aries_cloudagent/anoncreds/anoncreds/routes.py b/aries_cloudagent/anoncreds/anoncreds/routes.py index 9cf295139c..6a763cf34b 100644 --- a/aries_cloudagent/anoncreds/anoncreds/routes.py +++ b/aries_cloudagent/anoncreds/anoncreds/routes.py @@ -1,41 +1,133 @@ """ Anoncreds admin routes """ # import json +import logging from aiohttp import web -from aiohttp_apispec import (docs, # request_schema, response_schema - match_info_schema, querystring_schema) -from marshmallow import fields -from ...messaging.valid import ( UUIDFour +from aiohttp_apispec import ( + docs, + match_info_schema, + querystring_schema, + request_schema, + response_schema, ) +from marshmallow import fields + +from ...admin.request_context import AdminRequestContext from ...messaging.models.openapi import OpenAPISchema +from ...messaging.valid import ( + GENERIC_DID, + INDY_SCHEMA_ID, + INDY_VERSION, + NATURAL_NUM, + UUIDFour, +) + +LOGGER = logging.getLogger(__name__) SPEC_URI = "" -class SchemaIdMatchInfoSchema(OpenAPISchema): + +class SchemaIdMatchInfo(OpenAPISchema): """""" + schema_id = fields.Str( description="Schema identifier", required=True, example=UUIDFour.EXAMPLE ) - + + +class CredIdMatchInfo(OpenAPISchema): + """Path parameters and validators for request taking credential id.""" + + credential_id = fields.Str( + description="Credential identifier", required=True, example=UUIDFour.EXAMPLE + ) + + +class SchemaSchema(OpenAPISchema): + """Marshmallow schema for indy schema.""" + + attrNames = fields.List( + fields.Str( + description="Attribute name", + example="score", + ), + description="Schema attribute names", + data_key="attrNames", + ) + name = fields.Str( + description="Schema name", + example=INDY_SCHEMA_ID["example"].split(":")[2], + ) + version = fields.Str(description="Schema version", **INDY_VERSION) + issuerId = fields.Str( + description="Schema issuer did", **GENERIC_DID + ) # TODO: get correct validator + + +class SchemaPostQueryStringSchema(OpenAPISchema): + """""" + + schema = fields.Nested(SchemaSchema()) + options = fields.Dict( + description="Options ", + required=False, + ) + + +class SchemaResponseSchema(OpenAPISchema): + """""" + + schema = fields.Nested(SchemaSchema()) + options = fields.Dict( + description="Options ", + required=False, + ) + schema_id = fields.Str( + data_key="id", description="Schema identifier", **INDY_SCHEMA_ID + ) + resolution_metadata = fields.Dict() + schema_metadata = fields.Dict() + + +class SchemasQueryStringSchema(OpenAPISchema): + """""" + + schema_name = SchemaSchema.name + schema_version = SchemaSchema.version + schema_issuer_did = SchemaSchema.issuerId + @docs(tags=["anoncreds"], summary="") +@request_schema(SchemaPostQueryStringSchema()) async def schemas_post(request: web.BaseRequest): + context: AdminRequestContext = request["context"] + input = await request.json() + LOGGER.info(f"request mad with, {input}") + raise NotImplementedError() @docs(tags=["anoncreds"], summary="") -@match_info_schema(HolderCredIdMatchInfoSchema()) +@match_info_schema(SchemaIdMatchInfo()) +@response_schema(SchemaResponseSchema(), 200, description="") async def schema_get(request: web.BaseRequest): - raise NotImplementedError() - + context: AdminRequestContext = request["context"] + schema_id = request.match_info["schema_id"] -@docs(tags=["anoncreds"], summary="") -async def schemas_get(request: web.BaseRequest): + LOGGER.info(f"called with schema_id: {schema_id}") raise NotImplementedError() @docs(tags=["anoncreds"], summary="") -async def schemas_get_created(request: web.BaseRequest): +@querystring_schema(SchemasQueryStringSchema()) +async def schemas_get(request: web.BaseRequest): + context: AdminRequestContext = request["context"] + schema_name = request.query.get("schema_name") + schema_version = request.query.get("schema_version") + schema_issuer_did = request.query.get("schema_issuer_did") + LOGGER.info( + f"called with schema_name: {schema_name}, schema_version: {schema_version}, schema_issuer_did: {schema_issuer_did}" + ) raise NotImplementedError() @@ -61,14 +153,7 @@ async def register(app: web.Application): [ web.post("/anoncreds/schema", schemas_post, allow_head=False), web.get("/anoncreds/schema/{schema_id}", schema_get, allow_head=False), - web.get( - "/anoncreds/schemas/issuer/{issuer_id}", schemas_get, allow_head=False - ), - web.get( - "/anoncreds/schemas/did-method/{did_method}", - schemas_get, - allow_head=False, - ), + web.get("/anoncreds/schemas", schemas_get, allow_head=False), web.post( "/anoncreds/credential-definition", cred_def_post, allow_head=False ), @@ -78,12 +163,7 @@ async def register(app: web.Application): allow_head=False, ), web.get( - "/anoncreds/credential-definitions/issuer/{did_method}", - cred_defs_get, - allow_head=False, - ), - web.get( - "/anoncreds/credential-definitions/did-method/{did_method}", + "/anoncreds/credential-definitions/issuer/", cred_defs_get, allow_head=False, ), From 871a8b29e351f1291cccf4f1fa1f0e48ba27753f Mon Sep 17 00:00:00 2001 From: Adam Burdett Date: Mon, 6 Mar 2023 16:17:27 -0700 Subject: [PATCH 021/150] empty vars for cred def validation Signed-off-by: Adam Burdett --- .../anoncreds/anoncreds/routes.py | 107 +++++++++++++----- 1 file changed, 77 insertions(+), 30 deletions(-) diff --git a/aries_cloudagent/anoncreds/anoncreds/routes.py b/aries_cloudagent/anoncreds/anoncreds/routes.py index 6a763cf34b..23ef54f409 100644 --- a/aries_cloudagent/anoncreds/anoncreds/routes.py +++ b/aries_cloudagent/anoncreds/anoncreds/routes.py @@ -26,13 +26,13 @@ SPEC_URI = "" - +schema_id = fields.Str( + data_key="id", description="Schema identifier", **INDY_SCHEMA_ID +) class SchemaIdMatchInfo(OpenAPISchema): """""" - schema_id = fields.Str( - description="Schema identifier", required=True, example=UUIDFour.EXAMPLE - ) + schema_id = schema_id class CredIdMatchInfo(OpenAPISchema): @@ -43,25 +43,31 @@ class CredIdMatchInfo(OpenAPISchema): ) +schemaAttrNames = fields.List( + fields.Str( + description="Attribute name", + example="score", + ), + description="Schema attribute names", + data_key="attrNames", +) +schemaName = fields.Str( + description="Schema name", + example=INDY_SCHEMA_ID["example"].split(":")[2], +) +schemaVersion = fields.Str(description="Schema version", **INDY_VERSION) +issuerId = fields.Str( + description="Issuer did", **GENERIC_DID +) # TODO: get correct validator + + class SchemaSchema(OpenAPISchema): """Marshmallow schema for indy schema.""" - attrNames = fields.List( - fields.Str( - description="Attribute name", - example="score", - ), - description="Schema attribute names", - data_key="attrNames", - ) - name = fields.Str( - description="Schema name", - example=INDY_SCHEMA_ID["example"].split(":")[2], - ) - version = fields.Str(description="Schema version", **INDY_VERSION) - issuerId = fields.Str( - description="Schema issuer did", **GENERIC_DID - ) # TODO: get correct validator + attrNames = schemaAttrNames + name = schemaName + version = schemaVersion + issuerId = issuerId class SchemaPostQueryStringSchema(OpenAPISchema): @@ -72,8 +78,47 @@ class SchemaPostQueryStringSchema(OpenAPISchema): description="Options ", required=False, ) + +class CredDefSchema(OpenAPISchema): + """""" + + tag = + schemaId = schema_id + issuerId = issuerId + support_revocation = + revocation_registry_size = + +class CredDefPostOptionsSchema(OpenAPISchema): + """""" + endorser_connection_id = + support_revocation = + revocation_registry_size = + +class CredDefPostQueryStringSchema(OpenAPISchema): + """""" + + credential_definition = fields.Nested(CredDefSchema()) + options = fields.Nested(CredDefPostOptionsSchema()) - +class PublicKeysSchema(OpenAPISchema): + accumKey = fields.Dict( + example = '{ "z": "1 0BB...386"}' + ) +class RevRegValueSchema(OpenAPISchema): + """""" + publicKeys = PublicKeysSchema() + maxCredNum = #666, + tailsLocation = #"https://my.revocations.tails/tailsfile.txt", + tailsHash = #"91zvq2cFmBZmHCcLqFyzv7bfehHH5rMhdAG5wTjqy2PE" +class RevRegPostQueryStringSchema(OpenAPISchema): + """""" + + issuerId = issuerId + revocDefType = # "CL_ACCUM", + credDefId = #: "Gs6cQcvrtWoZKsbBhD3dQJ:3:CL:140384:mctc", + tag = # "MyCustomCredentialDefinition", + value= fields.Nested(RevRegValueSchema()) + class SchemaResponseSchema(OpenAPISchema): """""" @@ -82,9 +127,7 @@ class SchemaResponseSchema(OpenAPISchema): description="Options ", required=False, ) - schema_id = fields.Str( - data_key="id", description="Schema identifier", **INDY_SCHEMA_ID - ) + schema_id = schema_id resolution_metadata = fields.Dict() schema_metadata = fields.Dict() @@ -92,9 +135,9 @@ class SchemaResponseSchema(OpenAPISchema): class SchemasQueryStringSchema(OpenAPISchema): """""" - schema_name = SchemaSchema.name - schema_version = SchemaSchema.version - schema_issuer_did = SchemaSchema.issuerId + schemaName = schemaName + schemaVersion = schemaVersion + schemaIssuerDid = issuerId @docs(tags=["anoncreds"], summary="") @@ -122,9 +165,9 @@ async def schema_get(request: web.BaseRequest): @querystring_schema(SchemasQueryStringSchema()) async def schemas_get(request: web.BaseRequest): context: AdminRequestContext = request["context"] - schema_name = request.query.get("schema_name") - schema_version = request.query.get("schema_version") - schema_issuer_did = request.query.get("schema_issuer_did") + schema_name = request.query.get("schemaName") + schema_version = request.query.get("schemaVersion") + schema_issuer_did = request.query.get("schemaIssuerDid") LOGGER.info( f"called with schema_name: {schema_name}, schema_version: {schema_version}, schema_issuer_did: {schema_issuer_did}" ) @@ -132,17 +175,21 @@ async def schemas_get(request: web.BaseRequest): @docs(tags=["anoncreds"], summary="") +@request_schema(CredDefPostQueryStringSchema()) async def cred_def_post(request: web.BaseRequest): + context: AdminRequestContext = request["context"] raise NotImplementedError() @docs(tags=["anoncreds"], summary="") async def cred_def_get(request: web.BaseRequest): + context: AdminRequestContext = request["context"] raise NotImplementedError() @docs(tags=["anoncreds"], summary="") async def cred_defs_get(request: web.BaseRequest): + context: AdminRequestContext = request["context"] raise NotImplementedError() From 9067fd1dabd8e36bc68cd393756ab47cfe3a8ff5 Mon Sep 17 00:00:00 2001 From: Adam Burdett Date: Tue, 7 Mar 2023 15:57:33 -0700 Subject: [PATCH 022/150] anoncreds admin routes without expected return schemas Signed-off-by: Adam Burdett --- .../anoncreds/anoncreds/routes.py | 177 ++++++++++++------ 1 file changed, 116 insertions(+), 61 deletions(-) diff --git a/aries_cloudagent/anoncreds/anoncreds/routes.py b/aries_cloudagent/anoncreds/anoncreds/routes.py index 23ef54f409..fd55530138 100644 --- a/aries_cloudagent/anoncreds/anoncreds/routes.py +++ b/aries_cloudagent/anoncreds/anoncreds/routes.py @@ -16,9 +16,9 @@ from ...messaging.models.openapi import OpenAPISchema from ...messaging.valid import ( GENERIC_DID, + INDY_CRED_DEF_ID, INDY_SCHEMA_ID, INDY_VERSION, - NATURAL_NUM, UUIDFour, ) @@ -26,19 +26,19 @@ SPEC_URI = "" -schema_id = fields.Str( - data_key="id", description="Schema identifier", **INDY_SCHEMA_ID +schemaId = fields.Str( + data_key="schemaId", description="Schema identifier", **INDY_SCHEMA_ID ) -class SchemaIdMatchInfo(OpenAPISchema): - """""" - schema_id = schema_id + +class SchemaIdMatchInfo(OpenAPISchema): + schema_id = schemaId class CredIdMatchInfo(OpenAPISchema): """Path parameters and validators for request taking credential id.""" - credential_id = fields.Str( + cred_def_id = fields.Str( description="Credential identifier", required=True, example=UUIDFour.EXAMPLE ) @@ -57,7 +57,8 @@ class CredIdMatchInfo(OpenAPISchema): ) schemaVersion = fields.Str(description="Schema version", **INDY_VERSION) issuerId = fields.Str( - description="Issuer did", **GENERIC_DID + description="Issuer Identifier of the credential definition or schema", + **GENERIC_DID, ) # TODO: get correct validator @@ -70,55 +71,89 @@ class SchemaSchema(OpenAPISchema): issuerId = issuerId -class SchemaPostQueryStringSchema(OpenAPISchema): - """""" - - schema = fields.Nested(SchemaSchema()) - options = fields.Dict( - description="Options ", +class SchemaPostOptionSchema(OpenAPISchema): + endorser_connection_id = fields.UUID( + description="Connection identifier (optional)", required=False, + example=UUIDFour.EXAMPLE, ) - + + +class SchemaPostQueryStringSchema(OpenAPISchema): + schema = fields.Nested(SchemaSchema()) + options = fields.Nested(SchemaPostOptionSchema()) + + +support_revocation = fields.Bool() +revocation_registry_size = fields.Int() + + class CredDefSchema(OpenAPISchema): - """""" - - tag = - schemaId = schema_id + tag = fields.Str( + description="The tag value passed in by the Issuer to an AnonCred's Credential Definition create and store implementation." + ) + schemaId = schemaId issuerId = issuerId - support_revocation = - revocation_registry_size = + supportRevocation = support_revocation + revocationRegistrySize = revocation_registry_size + class CredDefPostOptionsSchema(OpenAPISchema): - """""" - endorser_connection_id = - support_revocation = - revocation_registry_size = - + endorserConnectionId = fields.Str() + supportRevocation = support_revocation + revocationRegistrySize = revocation_registry_size + + class CredDefPostQueryStringSchema(OpenAPISchema): - """""" - - credential_definition = fields.Nested(CredDefSchema()) + credentialDefinition = fields.Nested(CredDefSchema()) options = fields.Nested(CredDefPostOptionsSchema()) -class PublicKeysSchema(OpenAPISchema): - accumKey = fields.Dict( - example = '{ "z": "1 0BB...386"}' + +class CredDefsQueryStringSchema(OpenAPISchema): + credentialDefinitionId = fields.Str( + description="Credential definition identifier", + **INDY_CRED_DEF_ID, ) + issuerId = issuerId + schemaId = schemaId + schemaIssuerId = issuerId + schemaName = schemaName + schemaVersion = schemaVersion + + +# class CredDefResponseSchema(OpenAPISchema): + + +''' +class PublicKeysSchema(OpenAPISchema): + accumKey = fields.Dict(example='{ "z": "1 0BB...386"}') + + class RevRegValueSchema(OpenAPISchema): """""" + publicKeys = PublicKeysSchema() - maxCredNum = #666, - tailsLocation = #"https://my.revocations.tails/tailsfile.txt", - tailsHash = #"91zvq2cFmBZmHCcLqFyzv7bfehHH5rMhdAG5wTjqy2PE" + maxCredNum = fields.Int( + description=" The capacity of the Revocation Registry, a count of the number of credentials that can be issued using the Revocation Registry.", + example=777, + ) + tailsLocation = fields.URL( + description="The capacity of the Revocation Registry, a count of the number of credentials that can be issued using the Revocation Registry." + ) + tailsHash = fields.Str() # "91zvq2cFmBZmHCcLqFyzv7bfehHH5rMhdAG5wTjqy2PE" + + class RevRegPostQueryStringSchema(OpenAPISchema): """""" - + issuerId = issuerId - revocDefType = # "CL_ACCUM", - credDefId = #: "Gs6cQcvrtWoZKsbBhD3dQJ:3:CL:140384:mctc", - tag = # "MyCustomCredentialDefinition", - value= fields.Nested(RevRegValueSchema()) - + revocDefType = fields.Str() # always "CL_ACCUM", + credDefId = INDY_CRED_DEF_ID #: "Gs6cQcvrtWoZKsbBhD3dQJ:3:CL:140384:mctc", + tag = fields.Str() + value = fields.Nested(RevRegValueSchema()) +''' + + class SchemaResponseSchema(OpenAPISchema): """""" @@ -127,7 +162,7 @@ class SchemaResponseSchema(OpenAPISchema): description="Options ", required=False, ) - schema_id = schema_id + schema_id = schemaId resolution_metadata = fields.Dict() schema_metadata = fields.Dict() @@ -145,9 +180,8 @@ class SchemasQueryStringSchema(OpenAPISchema): async def schemas_post(request: web.BaseRequest): context: AdminRequestContext = request["context"] input = await request.json() - LOGGER.info(f"request mad with, {input}") - raise NotImplementedError() + return web.json_response({"input": input}) @docs(tags=["anoncreds"], summary="") @@ -155,42 +189,65 @@ async def schemas_post(request: web.BaseRequest): @response_schema(SchemaResponseSchema(), 200, description="") async def schema_get(request: web.BaseRequest): context: AdminRequestContext = request["context"] - schema_id = request.match_info["schema_id"] + schema_id = request.match_info["schemaId"] - LOGGER.info(f"called with schema_id: {schema_id}") - raise NotImplementedError() + return web.json_response({"schema_id": schema_id}) @docs(tags=["anoncreds"], summary="") @querystring_schema(SchemasQueryStringSchema()) async def schemas_get(request: web.BaseRequest): context: AdminRequestContext = request["context"] + schema_issuer_did = request.query.get("schemaIssuerDid") schema_name = request.query.get("schemaName") schema_version = request.query.get("schemaVersion") - schema_issuer_did = request.query.get("schemaIssuerDid") - LOGGER.info( - f"called with schema_name: {schema_name}, schema_version: {schema_version}, schema_issuer_did: {schema_issuer_did}" + + return web.json_response( + { + "schema_issuer_did": schema_issuer_did, + "schema_name": schema_name, + "schema_version": schema_version, + } ) - raise NotImplementedError() @docs(tags=["anoncreds"], summary="") @request_schema(CredDefPostQueryStringSchema()) async def cred_def_post(request: web.BaseRequest): context: AdminRequestContext = request["context"] - raise NotImplementedError() + input = await request.json() + return web.json_response({"input": input}) @docs(tags=["anoncreds"], summary="") +@match_info_schema(CredIdMatchInfo()) async def cred_def_get(request: web.BaseRequest): context: AdminRequestContext = request["context"] - raise NotImplementedError() + credential_id = request.match_info["cred_def_id"] + return web.json_response({"cred_def_id": credential_id}) @docs(tags=["anoncreds"], summary="") +@querystring_schema(CredDefsQueryStringSchema()) async def cred_defs_get(request: web.BaseRequest): context: AdminRequestContext = request["context"] - raise NotImplementedError() + cred_def_id = request.query.get("credentialDefinitionId") + issuer_id = request.query.get("issuerId") + schema_id = request.query.get("schemaId") + schema_issuer_id = request.query.get("schemaIssuerId") + schema_name = request.query.get("schemaName") + schema_version = request.query.get("schemaVersion") + + return web.json_response( + { + "cred_def_id": cred_def_id, + "issuer_id": issuer_id, + "schema_id": schema_id, + "schema_issuer_id": schema_issuer_id, + "schema_name": schema_name, + "schema_version": schema_version, + } + ) async def register(app: web.Application): @@ -198,19 +255,17 @@ async def register(app: web.Application): app.add_routes( [ - web.post("/anoncreds/schema", schemas_post, allow_head=False), - web.get("/anoncreds/schema/{schema_id}", schema_get, allow_head=False), + web.post("/anoncreds/schema", schemas_post), + web.get("/anoncreds/schema/{schemaId}", schema_get, allow_head=False), web.get("/anoncreds/schemas", schemas_get, allow_head=False), - web.post( - "/anoncreds/credential-definition", cred_def_post, allow_head=False - ), + web.post("/anoncreds/credential-definition", cred_def_post), web.get( - "/anoncreds/credential-definition/{credential_definition_id}", + "/anoncreds/credential-definition/{cred_def_id}", cred_def_get, allow_head=False, ), web.get( - "/anoncreds/credential-definitions/issuer/", + "/anoncreds/credential-definitions/", cred_defs_get, allow_head=False, ), From 9612213d06659e112219450d689070aaf280d239 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Tue, 7 Mar 2023 16:49:09 -0700 Subject: [PATCH 023/150] fix: remove unused import Signed-off-by: Char Howland --- aries_cloudagent/anoncreds/anoncreds/issuer.py | 1 - .../anoncreds/anoncreds/tests/test_cred_issuance.py | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/aries_cloudagent/anoncreds/anoncreds/issuer.py b/aries_cloudagent/anoncreds/anoncreds/issuer.py index 782c08cb8e..5e92df351d 100644 --- a/aries_cloudagent/anoncreds/anoncreds/issuer.py +++ b/aries_cloudagent/anoncreds/anoncreds/issuer.py @@ -18,7 +18,6 @@ RevocationRegistryDelta, Schema, ) -from ..issuer import AnonCredsIssuer from ...askar.profile import AskarProfile diff --git a/aries_cloudagent/anoncreds/anoncreds/tests/test_cred_issuance.py b/aries_cloudagent/anoncreds/anoncreds/tests/test_cred_issuance.py index fa2fded348..3082d048c9 100644 --- a/aries_cloudagent/anoncreds/anoncreds/tests/test_cred_issuance.py +++ b/aries_cloudagent/anoncreds/anoncreds/tests/test_cred_issuance.py @@ -92,6 +92,7 @@ async def setUp(self): assert "IndyCredxIssuer" in str(self.issuer) assert "IndyCredxVerifier" in str(self.verifier) + @pytest.mark.skip(reason="skipped due to anoncreds-rs issue pending resolution") async def test_issue_store_non_rev(self): assert ( self.issuer.make_schema_id(TEST_DID, SCHEMA_NAME, SCHEMA_VERSION) @@ -193,6 +194,7 @@ async def test_issue_store_non_rev(self): await self.holder.delete_credential(cred_id) + @pytest.mark.skip(reason="skipped due to anoncreds-rs issue pending resolution") async def test_issue_store_rev(self): assert ( self.issuer.make_schema_id(TEST_DID, SCHEMA_NAME, SCHEMA_VERSION) From 4ed41c3e01ab9b3bbdd4fe77df72be623d9b0615 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Wed, 8 Mar 2023 14:27:52 -0700 Subject: [PATCH 024/150] feat: add plugin structure Signed-off-by: Char Howland --- .../anoncreds/anoncreds/__init__.py | 30 +++++++ .../anoncreds/anoncreds/anoncreds_objects.py | 80 +++++++++++++++++++ .../anoncreds/anoncreds/anoncreds_registry.py | 19 +++++ .../anoncreds/anoncreds/base_registry.py | 57 +++++++++++++ .../anoncreds/did_indy_registry/__init__.py | 0 .../anoncreds/did_indy_registry/definition.py | 10 +++ .../did_indy_registry/v1_0/__init__.py | 0 .../v1_0/did_indy_registry.py | 47 +++++++++++ .../did_indy_registry/v1_0/routes.py | 0 .../anoncreds/did_web_registry/__init__.py | 0 .../anoncreds/did_web_registry/definition.py | 10 +++ .../did_web_registry/v1_0/__init__.py | 0 .../did_web_registry/v1_0/did_web_registry.py | 47 +++++++++++ .../anoncreds/did_web_registry/v1_0/routes.py | 0 aries_cloudagent/config/default_context.py | 9 +++ 15 files changed, 309 insertions(+) create mode 100644 aries_cloudagent/anoncreds/anoncreds/anoncreds_objects.py create mode 100644 aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py create mode 100644 aries_cloudagent/anoncreds/anoncreds/base_registry.py create mode 100644 aries_cloudagent/anoncreds/did_indy_registry/__init__.py create mode 100644 aries_cloudagent/anoncreds/did_indy_registry/definition.py create mode 100644 aries_cloudagent/anoncreds/did_indy_registry/v1_0/__init__.py create mode 100644 aries_cloudagent/anoncreds/did_indy_registry/v1_0/did_indy_registry.py create mode 100644 aries_cloudagent/anoncreds/did_indy_registry/v1_0/routes.py create mode 100644 aries_cloudagent/anoncreds/did_web_registry/__init__.py create mode 100644 aries_cloudagent/anoncreds/did_web_registry/definition.py create mode 100644 aries_cloudagent/anoncreds/did_web_registry/v1_0/__init__.py create mode 100644 aries_cloudagent/anoncreds/did_web_registry/v1_0/did_web_registry.py create mode 100644 aries_cloudagent/anoncreds/did_web_registry/v1_0/routes.py diff --git a/aries_cloudagent/anoncreds/anoncreds/__init__.py b/aries_cloudagent/anoncreds/anoncreds/__init__.py index e69de29bb2..3992d2d9e4 100644 --- a/aries_cloudagent/anoncreds/anoncreds/__init__.py +++ b/aries_cloudagent/anoncreds/anoncreds/__init__.py @@ -0,0 +1,30 @@ +import logging + +from ...config.injection_context import InjectionContext +from ...config.provider import ClassProvider + +from ..anoncreds.anoncreds_registry import AnonCredsRegistry + +LOGGER = logging.getLogger(__name__) + + +async def setup(context: InjectionContext): + """Set up default resolvers.""" + registry = context.inject_or(AnonCredsRegistry) + if not registry: + LOGGER.warning("No DID Resolver instance found in context") + return + + indy_registry = ClassProvider( + "aries_cloudagent.anoncreds.did_indy_registry.v1_0.did_indy_registry.DIDIndyRegistry" + ).provide(context.settings, context.injector) + await indy_registry.setup(context) + registry.register_registry(indy_registry) + + web_registry = ClassProvider( + "aries_cloudagent.anoncreds.did_web_registry.v1_0.did_web_registry.DIDWebRegistry" + ).provide(context.settings, context.injector) + await web_registry.setup(context) + registry.register_registry(web_registry) + + # TODO: add context.settings diff --git a/aries_cloudagent/anoncreds/anoncreds/anoncreds_objects.py b/aries_cloudagent/anoncreds/anoncreds/anoncreds_objects.py new file mode 100644 index 0000000000..c074c621d1 --- /dev/null +++ b/aries_cloudagent/anoncreds/anoncreds/anoncreds_objects.py @@ -0,0 +1,80 @@ +from typing import Any, Dict, List, Optional +from typing_extensions import Literal +from dataclasses import dataclass + + +@dataclass +class AnonCredsSchema: + issuerId: str + attrNames: List[str] + name: str + version: str + + +@dataclass +class AnonCredsRegistryGetSchema: + schema: AnonCredsSchema + schema_id: str + resolution_metadata: Dict[str, Any] + schema_metadata: Dict[str, Any] + + +# TODO: determine types for `primary` and `revocation` +@dataclass +class AnonCredsCredentialDefinitionValue: + primary: Any + revocation: Optional[Any] + + +@dataclass +class AnonCredsCredentialDefinition: + issuerId: str + schemaId: str + type: Literal["CL"] + tag: str + value: AnonCredsCredentialDefinitionValue + + +@dataclass +class AnonCredsRegistryGetCredentialDefinition: + credential_definition: AnonCredsCredentialDefinition + credential_definition_id: str + resolution_metadata: Dict[str, Any] + credential_definition_metadata: Dict[str, Any] + + +@dataclass +class AnonCredsRevocationRegistryDefinition: + issuerId: str + type: Literal["CL_ACCUM"] + credDefId: str + tag: str + # TODO: determine type for `publicKeys` + publicKeys: Any + maxCredNum: int + tailsLocation: str + tailsHash: str + + +@dataclass +class AnonCredsRegistryGetRevocationRegistryDefinition: + revocation_registry: AnonCredsRevocationRegistryDefinition + revocation_registry_id: str + resolution_metadata: Dict[str, Any] + revocation_registry_metadata: Dict[str, Any] + + +@dataclass +class AnonCredsRevocationList: + issuerId: str + revRegId: str + revocationList: List[int] + currentAccumulator: str + timestamp: int + + +@dataclass +class AnonCredsRegistryGetRevocationList: + revocation_list: AnonCredsRevocationList + resolution_metadata: Dict[str, Any] + revocation_registry_metadata: Dict[str, Any] diff --git a/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py b/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py new file mode 100644 index 0000000000..4430f05a7c --- /dev/null +++ b/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py @@ -0,0 +1,19 @@ +import logging +from typing import List + +from .base_registry import BaseRegistry + +LOGGER = logging.getLogger(__name__) + + +class AnonCredsRegistry: + def __init__(self, registries: List[BaseRegistry] = None): + """Create DID Resolver.""" + self.registries = registries or [] + # TODO: add schema and cred def registries + + def register_registry(self, registry: BaseRegistry): + """Register a new registry.""" + self.registries.append(registry) + + # TODO: add logic for picking the correct registry diff --git a/aries_cloudagent/anoncreds/anoncreds/base_registry.py b/aries_cloudagent/anoncreds/anoncreds/base_registry.py new file mode 100644 index 0000000000..9b8177da7d --- /dev/null +++ b/aries_cloudagent/anoncreds/anoncreds/base_registry.py @@ -0,0 +1,57 @@ +from abc import abstractmethod +from ...config.injection_context import InjectionContext + +from .anoncreds_objects import ( + AnonCredsRegistryGetCredentialDefinition, + AnonCredsRegistryGetRevocationList, + AnonCredsRegistryGetRevocationRegistryDefinition, + AnonCredsRegistryGetSchema, +) + + +class BaseRegistry: + @abstractmethod + async def setup(self, context: InjectionContext): + """Setup method""" + + @abstractmethod + async def get_schema(self, schema_id: str) -> AnonCredsRegistryGetSchema: + """Get a schema from the registry.""" + + # TODO: determine keyword arguments + @abstractmethod + async def register_schema(self): + """Register a schema on the registry.""" + + @abstractmethod + async def get_credential_definition( + self, credential_definition_id: str + ) -> AnonCredsRegistryGetCredentialDefinition: + """Get a credential definition from the registry.""" + + # TODO: determine keyword arguments + @abstractmethod + async def register_credential_definition(self): + """Register a credential definition on the registry.""" + + @abstractmethod + async def get_revocation_registry_definition( + self, revocation_registry_id: str + ) -> AnonCredsRegistryGetRevocationRegistryDefinition: + """Get a revocation registry definition from the registry.""" + + # TODO: determine keyword arguments + @abstractmethod + async def register_revocation_registry_definition(self): + """Register a revocation registry definition on the registry.""" + + @abstractmethod + async def get_revocation_list( + self, revocation_registry_id: str, timestamp: str + ) -> AnonCredsRegistryGetRevocationList: + """Get a revocation list from the registry.""" + + # TODO: determine keyword arguments + @abstractmethod + async def register_revocation_list(self): + """Register a revocation list on the registry.""" diff --git a/aries_cloudagent/anoncreds/did_indy_registry/__init__.py b/aries_cloudagent/anoncreds/did_indy_registry/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aries_cloudagent/anoncreds/did_indy_registry/definition.py b/aries_cloudagent/anoncreds/did_indy_registry/definition.py new file mode 100644 index 0000000000..62bddef6f5 --- /dev/null +++ b/aries_cloudagent/anoncreds/did_indy_registry/definition.py @@ -0,0 +1,10 @@ +"""Version definitions for this protocol.""" + +versions = [ + { + "major_version": 1, + "minimum_minor_version": 0, + "current_minor_version": 0, + "path": "v1_0", + } +] diff --git a/aries_cloudagent/anoncreds/did_indy_registry/v1_0/__init__.py b/aries_cloudagent/anoncreds/did_indy_registry/v1_0/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aries_cloudagent/anoncreds/did_indy_registry/v1_0/did_indy_registry.py b/aries_cloudagent/anoncreds/did_indy_registry/v1_0/did_indy_registry.py new file mode 100644 index 0000000000..7c042e8a75 --- /dev/null +++ b/aries_cloudagent/anoncreds/did_indy_registry/v1_0/did_indy_registry.py @@ -0,0 +1,47 @@ +from ...anoncreds.anoncreds_objects import ( + AnonCredsRegistryGetCredentialDefinition, + AnonCredsRegistryGetRevocationList, + AnonCredsRegistryGetRevocationRegistryDefinition, + AnonCredsRegistryGetSchema, +) +from ....config.injection_context import InjectionContext +from ....anoncreds.anoncreds.anoncreds_registry import BaseRegistry + + +class DIDIndyRegistry(BaseRegistry): + async def setup(self, context: InjectionContext): + print("Successfully registered DIDIndyRegistry") + + async def get_schema(self, schema_id: str) -> AnonCredsRegistryGetSchema: + """Get a schema from the registry.""" + + # TODO: determine keyword arguments + async def register_schema(self): + """Register a schema on the registry.""" + + async def get_credential_definition( + self, credential_definition_id: str + ) -> AnonCredsRegistryGetCredentialDefinition: + """Get a credential definition from the registry.""" + + # TODO: determine keyword arguments + async def register_credential_definition(self): + """Register a credential definition on the registry.""" + + async def get_revocation_registry_definition( + self, revocation_registry_id: str + ) -> AnonCredsRegistryGetRevocationRegistryDefinition: + """Get a revocation registry definition from the registry.""" + + # TODO: determine keyword arguments + async def register_revocation_registry_definition(self): + """Register a revocation registry definition on the registry.""" + + async def get_revocation_list( + self, revocation_registry_id: str, timestamp: str + ) -> AnonCredsRegistryGetRevocationList: + """Get a revocation list from the registry.""" + + # TODO: determine keyword arguments + async def register_revocation_list(self): + """Register a revocation list on the registry.""" diff --git a/aries_cloudagent/anoncreds/did_indy_registry/v1_0/routes.py b/aries_cloudagent/anoncreds/did_indy_registry/v1_0/routes.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aries_cloudagent/anoncreds/did_web_registry/__init__.py b/aries_cloudagent/anoncreds/did_web_registry/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aries_cloudagent/anoncreds/did_web_registry/definition.py b/aries_cloudagent/anoncreds/did_web_registry/definition.py new file mode 100644 index 0000000000..62bddef6f5 --- /dev/null +++ b/aries_cloudagent/anoncreds/did_web_registry/definition.py @@ -0,0 +1,10 @@ +"""Version definitions for this protocol.""" + +versions = [ + { + "major_version": 1, + "minimum_minor_version": 0, + "current_minor_version": 0, + "path": "v1_0", + } +] diff --git a/aries_cloudagent/anoncreds/did_web_registry/v1_0/__init__.py b/aries_cloudagent/anoncreds/did_web_registry/v1_0/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aries_cloudagent/anoncreds/did_web_registry/v1_0/did_web_registry.py b/aries_cloudagent/anoncreds/did_web_registry/v1_0/did_web_registry.py new file mode 100644 index 0000000000..4585625a60 --- /dev/null +++ b/aries_cloudagent/anoncreds/did_web_registry/v1_0/did_web_registry.py @@ -0,0 +1,47 @@ +from ...anoncreds.anoncreds_objects import ( + AnonCredsRegistryGetCredentialDefinition, + AnonCredsRegistryGetRevocationList, + AnonCredsRegistryGetRevocationRegistryDefinition, + AnonCredsRegistryGetSchema, +) +from ....config.injection_context import InjectionContext +from ....anoncreds.anoncreds.anoncreds_registry import BaseRegistry + + +class DIDWebRegistry(BaseRegistry): + async def setup(self, context: InjectionContext): + print("Successfully registered DIDWebRegistry") + + async def get_schema(self, schema_id: str) -> AnonCredsRegistryGetSchema: + """Get a schema from the registry.""" + + # TODO: determine keyword arguments + async def register_schema(self): + """Register a schema on the registry.""" + + async def get_credential_definition( + self, credential_definition_id: str + ) -> AnonCredsRegistryGetCredentialDefinition: + """Get a credential definition from the registry.""" + + # TODO: determine keyword arguments + async def register_credential_definition(self): + """Register a credential definition on the registry.""" + + async def get_revocation_registry_definition( + self, revocation_registry_id: str + ) -> AnonCredsRegistryGetRevocationRegistryDefinition: + """Get a revocation registry definition from the registry.""" + + # TODO: determine keyword arguments + async def register_revocation_registry_definition(self): + """Register a revocation registry definition on the registry.""" + + async def get_revocation_list( + self, revocation_registry_id: str, timestamp: str + ) -> AnonCredsRegistryGetRevocationList: + """Get a revocation list from the registry.""" + + # TODO: determine keyword arguments + async def register_revocation_list(self): + """Register a revocation list on the registry.""" diff --git a/aries_cloudagent/anoncreds/did_web_registry/v1_0/routes.py b/aries_cloudagent/anoncreds/did_web_registry/v1_0/routes.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aries_cloudagent/config/default_context.py b/aries_cloudagent/config/default_context.py index 360f103fa1..99cd638d76 100644 --- a/aries_cloudagent/config/default_context.py +++ b/aries_cloudagent/config/default_context.py @@ -1,5 +1,6 @@ """Classes for configuring the default injection context.""" +from ..anoncreds.anoncreds.anoncreds_registry import AnonCredsRegistry from ..cache.base import BaseCache from ..cache.in_memory import InMemoryCache from ..core.event_bus import EventBus @@ -51,6 +52,7 @@ async def build_context(self) -> InjectionContext: # Global did resolver context.injector.bind_instance(DIDResolver, DIDResolver([])) + context.injector.bind_instance(AnonCredsRegistry, AnonCredsRegistry()) context.injector.bind_instance(DIDMethods, DIDMethods()) context.injector.bind_instance(KeyTypes, KeyTypes()) @@ -127,6 +129,13 @@ async def load_plugins(self, context: InjectionContext): plugin_registry.register_plugin("aries_cloudagent.revocation") plugin_registry.register_plugin("aries_cloudagent.resolver") plugin_registry.register_plugin("aries_cloudagent.wallet") + plugin_registry.register_plugin("aries_cloudagent.anoncreds.anoncreds") + plugin_registry.register_plugin("aries_cloudagent.anoncreds.did_indy_registry") + plugin_registry.register_plugin("aries_cloudagent.anoncreds.did_web_registry") + + # TODO: add anoncreds_plugins to argparse settings + for plugin_path in self.settings.get("anoncreds_plugins", []): + plugin_registry.register_plugin(plugin_path) if context.settings.get("multitenant.admin_enabled"): plugin_registry.register_plugin("aries_cloudagent.multitenant.admin") From 0f59de81319518556ca7677bb5788c1d48502975 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Wed, 8 Mar 2023 14:52:30 -0700 Subject: [PATCH 025/150] fix: flake8 Signed-off-by: Char Howland --- aries_cloudagent/anoncreds/anoncreds/__init__.py | 3 ++- .../anoncreds/anoncreds/anoncreds_objects.py | 10 ++++++++++ .../anoncreds/anoncreds/anoncreds_registry.py | 2 ++ aries_cloudagent/anoncreds/anoncreds/base_registry.py | 4 +++- .../did_indy_registry/v1_0/did_indy_registry.py | 3 +++ .../anoncreds/did_indy_registry/v1_0/routes.py | 1 + .../did_web_registry/v1_0/did_web_registry.py | 3 +++ .../anoncreds/did_web_registry/v1_0/routes.py | 1 + 8 files changed, 25 insertions(+), 2 deletions(-) diff --git a/aries_cloudagent/anoncreds/anoncreds/__init__.py b/aries_cloudagent/anoncreds/anoncreds/__init__.py index 3992d2d9e4..117bf7ec8e 100644 --- a/aries_cloudagent/anoncreds/anoncreds/__init__.py +++ b/aries_cloudagent/anoncreds/anoncreds/__init__.py @@ -16,7 +16,8 @@ async def setup(context: InjectionContext): return indy_registry = ClassProvider( - "aries_cloudagent.anoncreds.did_indy_registry.v1_0.did_indy_registry.DIDIndyRegistry" + "aries_cloudagent.anoncreds.did_indy_registry.v1_0.did_indy_registry." + "DIDIndyRegistry" ).provide(context.settings, context.injector) await indy_registry.setup(context) registry.register_registry(indy_registry) diff --git a/aries_cloudagent/anoncreds/anoncreds/anoncreds_objects.py b/aries_cloudagent/anoncreds/anoncreds/anoncreds_objects.py index c074c621d1..c09a362cee 100644 --- a/aries_cloudagent/anoncreds/anoncreds/anoncreds_objects.py +++ b/aries_cloudagent/anoncreds/anoncreds/anoncreds_objects.py @@ -1,3 +1,4 @@ +"""AnonCreds Objects""" from typing import Any, Dict, List, Optional from typing_extensions import Literal from dataclasses import dataclass @@ -5,6 +6,7 @@ @dataclass class AnonCredsSchema: + """AnonCredsSchema""" issuerId: str attrNames: List[str] name: str @@ -13,6 +15,7 @@ class AnonCredsSchema: @dataclass class AnonCredsRegistryGetSchema: + """AnonCredsRegistryGetSchema""" schema: AnonCredsSchema schema_id: str resolution_metadata: Dict[str, Any] @@ -22,12 +25,14 @@ class AnonCredsRegistryGetSchema: # TODO: determine types for `primary` and `revocation` @dataclass class AnonCredsCredentialDefinitionValue: + """"AnonCredsCredentialDefinitionValue""" primary: Any revocation: Optional[Any] @dataclass class AnonCredsCredentialDefinition: + """AnonCredsCredentialDefinition""" issuerId: str schemaId: str type: Literal["CL"] @@ -37,6 +42,7 @@ class AnonCredsCredentialDefinition: @dataclass class AnonCredsRegistryGetCredentialDefinition: + """AnonCredsRegistryGetCredentialDefinition""" credential_definition: AnonCredsCredentialDefinition credential_definition_id: str resolution_metadata: Dict[str, Any] @@ -45,6 +51,7 @@ class AnonCredsRegistryGetCredentialDefinition: @dataclass class AnonCredsRevocationRegistryDefinition: + """AnonCredsRevocationRegistryDefinition""" issuerId: str type: Literal["CL_ACCUM"] credDefId: str @@ -58,6 +65,7 @@ class AnonCredsRevocationRegistryDefinition: @dataclass class AnonCredsRegistryGetRevocationRegistryDefinition: + """AnonCredsRegistryGetRevocationRegistryDefinition""" revocation_registry: AnonCredsRevocationRegistryDefinition revocation_registry_id: str resolution_metadata: Dict[str, Any] @@ -66,6 +74,7 @@ class AnonCredsRegistryGetRevocationRegistryDefinition: @dataclass class AnonCredsRevocationList: + """AnonCredsRevocationList""" issuerId: str revRegId: str revocationList: List[int] @@ -75,6 +84,7 @@ class AnonCredsRevocationList: @dataclass class AnonCredsRegistryGetRevocationList: + """AnonCredsRegistryGetRevocationList""" revocation_list: AnonCredsRevocationList resolution_metadata: Dict[str, Any] revocation_registry_metadata: Dict[str, Any] diff --git a/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py b/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py index 4430f05a7c..a7902bc18b 100644 --- a/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py @@ -1,3 +1,4 @@ +"""AnonCreds Registry""" import logging from typing import List @@ -7,6 +8,7 @@ class AnonCredsRegistry: + """AnonCredsRegistry""" def __init__(self, registries: List[BaseRegistry] = None): """Create DID Resolver.""" self.registries = registries or [] diff --git a/aries_cloudagent/anoncreds/anoncreds/base_registry.py b/aries_cloudagent/anoncreds/anoncreds/base_registry.py index 9b8177da7d..ba8174ef5f 100644 --- a/aries_cloudagent/anoncreds/anoncreds/base_registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/base_registry.py @@ -1,3 +1,4 @@ +"""Base Registry""" from abc import abstractmethod from ...config.injection_context import InjectionContext @@ -10,9 +11,10 @@ class BaseRegistry: + """BaseRegistry""" @abstractmethod async def setup(self, context: InjectionContext): - """Setup method""" + """Setup method.""" @abstractmethod async def get_schema(self, schema_id: str) -> AnonCredsRegistryGetSchema: diff --git a/aries_cloudagent/anoncreds/did_indy_registry/v1_0/did_indy_registry.py b/aries_cloudagent/anoncreds/did_indy_registry/v1_0/did_indy_registry.py index 7c042e8a75..5363adbd5a 100644 --- a/aries_cloudagent/anoncreds/did_indy_registry/v1_0/did_indy_registry.py +++ b/aries_cloudagent/anoncreds/did_indy_registry/v1_0/did_indy_registry.py @@ -1,3 +1,4 @@ +"""DID Indy Registry""" from ...anoncreds.anoncreds_objects import ( AnonCredsRegistryGetCredentialDefinition, AnonCredsRegistryGetRevocationList, @@ -9,7 +10,9 @@ class DIDIndyRegistry(BaseRegistry): + """DIDIndyRegistry""" async def setup(self, context: InjectionContext): + """Setup.""" print("Successfully registered DIDIndyRegistry") async def get_schema(self, schema_id: str) -> AnonCredsRegistryGetSchema: diff --git a/aries_cloudagent/anoncreds/did_indy_registry/v1_0/routes.py b/aries_cloudagent/anoncreds/did_indy_registry/v1_0/routes.py index e69de29bb2..9d3bc1ba85 100644 --- a/aries_cloudagent/anoncreds/did_indy_registry/v1_0/routes.py +++ b/aries_cloudagent/anoncreds/did_indy_registry/v1_0/routes.py @@ -0,0 +1 @@ +"""Routes for DID Indy Registry""" \ No newline at end of file diff --git a/aries_cloudagent/anoncreds/did_web_registry/v1_0/did_web_registry.py b/aries_cloudagent/anoncreds/did_web_registry/v1_0/did_web_registry.py index 4585625a60..60ff7d7b92 100644 --- a/aries_cloudagent/anoncreds/did_web_registry/v1_0/did_web_registry.py +++ b/aries_cloudagent/anoncreds/did_web_registry/v1_0/did_web_registry.py @@ -1,3 +1,4 @@ +"""DID Web Registry""" from ...anoncreds.anoncreds_objects import ( AnonCredsRegistryGetCredentialDefinition, AnonCredsRegistryGetRevocationList, @@ -9,7 +10,9 @@ class DIDWebRegistry(BaseRegistry): + """DIDWebRegistry""" async def setup(self, context: InjectionContext): + """Setup.""" print("Successfully registered DIDWebRegistry") async def get_schema(self, schema_id: str) -> AnonCredsRegistryGetSchema: diff --git a/aries_cloudagent/anoncreds/did_web_registry/v1_0/routes.py b/aries_cloudagent/anoncreds/did_web_registry/v1_0/routes.py index e69de29bb2..def7cc9e24 100644 --- a/aries_cloudagent/anoncreds/did_web_registry/v1_0/routes.py +++ b/aries_cloudagent/anoncreds/did_web_registry/v1_0/routes.py @@ -0,0 +1 @@ +"""Routes for DID Web Registry""" \ No newline at end of file From 4f96ed2e858a00a36b86a4d88d8e598d5f7979b7 Mon Sep 17 00:00:00 2001 From: Adam Burdett Date: Wed, 8 Mar 2023 17:39:50 -0700 Subject: [PATCH 026/150] routes response schemas Signed-off-by: Adam Burdett --- .../anoncreds/anoncreds/routes.py | 99 +++++++++++++++++-- 1 file changed, 90 insertions(+), 9 deletions(-) diff --git a/aries_cloudagent/anoncreds/anoncreds/routes.py b/aries_cloudagent/anoncreds/anoncreds/routes.py index fd55530138..5d5e6554f2 100644 --- a/aries_cloudagent/anoncreds/anoncreds/routes.py +++ b/aries_cloudagent/anoncreds/anoncreds/routes.py @@ -109,11 +109,14 @@ class CredDefPostQueryStringSchema(OpenAPISchema): options = fields.Nested(CredDefPostOptionsSchema()) +credentialDefinitionId = fields.Str( + description="Credential definition identifier", + **INDY_CRED_DEF_ID, +) + + class CredDefsQueryStringSchema(OpenAPISchema): - credentialDefinitionId = fields.Str( - description="Credential definition identifier", - **INDY_CRED_DEF_ID, - ) + credentialDefinitionId = credentialDefinitionId issuerId = issuerId schemaId = schemaId schemaIssuerId = issuerId @@ -121,7 +124,65 @@ class CredDefsQueryStringSchema(OpenAPISchema): schemaVersion = schemaVersion -# class CredDefResponseSchema(OpenAPISchema): +""" +"primary": { + "n": "779...397", + "r": { + "link_secret": "521...922", + "": "410...200" + }, + "rctxt": "774...977", + "s": "750..893", + "z": "632...005" +} +""" + + +class PrimarySchema(OpenAPISchema): + n = fields.Str(example="779...397") + r = fields.Dict() + rctxt = fields.Str(example="774...977") + s = fields.Str(example="750..893") + z = fields.Str(example="632...005") + + +class CredDefValueSchema(OpenAPISchema): + primary = fields.Nested(PrimarySchema()) + + +class CredDefResponseSchema(OpenAPISchema): + issuerId = issuerId + schemaId = schemaId + tag = fields.Str( + description="The tag value passed in by the Issuer to an AnonCred's Credential Definition create and store implementation." + ) + value = fields.Nested(CredDefValueSchema()) + # registration_metadata = support_revocation + # revocationRegistrySize = revocation_registry_size + + +class CredDefState(OpenAPISchema): + state = fields.Str() # TODO: create validator for only possible states + credential_definition_id = credentialDefinitionId + credential_definition = fields.Nested(CredDefResponseSchema()) + + +class PostCredDefResponseSchema(OpenAPISchema): + job_id = fields.Str() + credential_definition_state = fields.Nested(CredDefState()) + registration_metadata = fields.Dict() + credential_definition_metadata = fields.Dict() + + +class GetCredDefResponseSchema(OpenAPISchema): + credential_definition_id = credentialDefinitionId + credential_definition = fields.Nested(CredDefResponseSchema()) + resolution_metadata = fields.Dict() + credential_definition_metadata = fields.Dict() + + +class GetCredDefsResponseSchema(OpenAPISchema): + credential_definition_id = fields.Str() ''' @@ -154,9 +215,22 @@ class RevRegPostQueryStringSchema(OpenAPISchema): ''' -class SchemaResponseSchema(OpenAPISchema): - """""" +class SchemaState(OpenAPISchema): + state = fields.Str() # TODO: create validator for only possible states + schema_id = schemaId + schema = fields.Nested(SchemaSchema()) + +class PostSchemaResponseSchema(OpenAPISchema): + job_id = fields.Str() + schema_state = fields.Nested(SchemaState()) + registration_metadata = ( + fields.Dict() + ) # For indy, schema_metadata will contain the seqNo + schema_metadata = fields.Dict() + + +class SchemaResponseSchema(OpenAPISchema): schema = fields.Nested(SchemaSchema()) options = fields.Dict( description="Options ", @@ -167,9 +241,11 @@ class SchemaResponseSchema(OpenAPISchema): schema_metadata = fields.Dict() -class SchemasQueryStringSchema(OpenAPISchema): - """""" +class SchemasResponseSchema(OpenAPISchema): + schema_id = fields.List(schemaId) + +class SchemasQueryStringSchema(OpenAPISchema): schemaName = schemaName schemaVersion = schemaVersion schemaIssuerDid = issuerId @@ -177,6 +253,7 @@ class SchemasQueryStringSchema(OpenAPISchema): @docs(tags=["anoncreds"], summary="") @request_schema(SchemaPostQueryStringSchema()) +@response_schema(PostSchemaResponseSchema(), 200, description="") async def schemas_post(request: web.BaseRequest): context: AdminRequestContext = request["context"] input = await request.json() @@ -196,6 +273,7 @@ async def schema_get(request: web.BaseRequest): @docs(tags=["anoncreds"], summary="") @querystring_schema(SchemasQueryStringSchema()) +@response_schema(SchemasResponseSchema(), 200, description="") async def schemas_get(request: web.BaseRequest): context: AdminRequestContext = request["context"] schema_issuer_did = request.query.get("schemaIssuerDid") @@ -213,6 +291,7 @@ async def schemas_get(request: web.BaseRequest): @docs(tags=["anoncreds"], summary="") @request_schema(CredDefPostQueryStringSchema()) +@response_schema(PostCredDefResponseSchema(), 200, description="") async def cred_def_post(request: web.BaseRequest): context: AdminRequestContext = request["context"] input = await request.json() @@ -221,6 +300,7 @@ async def cred_def_post(request: web.BaseRequest): @docs(tags=["anoncreds"], summary="") @match_info_schema(CredIdMatchInfo()) +@response_schema(GetCredDefResponseSchema(), 200, description="") async def cred_def_get(request: web.BaseRequest): context: AdminRequestContext = request["context"] credential_id = request.match_info["cred_def_id"] @@ -229,6 +309,7 @@ async def cred_def_get(request: web.BaseRequest): @docs(tags=["anoncreds"], summary="") @querystring_schema(CredDefsQueryStringSchema()) +@response_schema(GetCredDefsResponseSchema(), 200, description="") async def cred_defs_get(request: web.BaseRequest): context: AdminRequestContext = request["context"] cred_def_id = request.query.get("credentialDefinitionId") From c56ac544638562c90166373fdda970a68f2dead7 Mon Sep 17 00:00:00 2001 From: Adam Burdett Date: Thu, 9 Mar 2023 10:23:08 -0700 Subject: [PATCH 027/150] happy flake8 doc strings Signed-off-by: Adam Burdett --- .../anoncreds/anoncreds/routes.py | 191 ++++++++++++------ 1 file changed, 128 insertions(+), 63 deletions(-) diff --git a/aries_cloudagent/anoncreds/anoncreds/routes.py b/aries_cloudagent/anoncreds/anoncreds/routes.py index 5d5e6554f2..ef7d9ad5cc 100644 --- a/aries_cloudagent/anoncreds/anoncreds/routes.py +++ b/aries_cloudagent/anoncreds/anoncreds/routes.py @@ -1,5 +1,4 @@ -""" Anoncreds admin routes """ -# import json +"""Anoncreds admin routes.""" import logging from aiohttp import web @@ -12,7 +11,7 @@ ) from marshmallow import fields -from ...admin.request_context import AdminRequestContext +# from ...admin.request_context import AdminRequestContext from ...messaging.models.openapi import OpenAPISchema from ...messaging.valid import ( GENERIC_DID, @@ -32,6 +31,8 @@ class SchemaIdMatchInfo(OpenAPISchema): + """Path parameters and validators for request taking schema id.""" + schema_id = schemaId @@ -72,6 +73,8 @@ class SchemaSchema(OpenAPISchema): class SchemaPostOptionSchema(OpenAPISchema): + """Parameters and validators for schema options.""" + endorser_connection_id = fields.UUID( description="Connection identifier (optional)", required=False, @@ -80,6 +83,8 @@ class SchemaPostOptionSchema(OpenAPISchema): class SchemaPostQueryStringSchema(OpenAPISchema): + """Parameters and validators for query string in create schema.""" + schema = fields.Nested(SchemaSchema()) options = fields.Nested(SchemaPostOptionSchema()) @@ -89,8 +94,11 @@ class SchemaPostQueryStringSchema(OpenAPISchema): class CredDefSchema(OpenAPISchema): + """Parameters and validators for credential definition.""" + tag = fields.Str( - description="The tag value passed in by the Issuer to an AnonCred's Credential Definition create and store implementation." + description="""The tag value passed in by the Issuer to + an AnonCred's Credential Definition create and store implementation.""" ) schemaId = schemaId issuerId = issuerId @@ -99,12 +107,16 @@ class CredDefSchema(OpenAPISchema): class CredDefPostOptionsSchema(OpenAPISchema): + """Parameters and validators for credential definition options.""" + endorserConnectionId = fields.Str() supportRevocation = support_revocation revocationRegistrySize = revocation_registry_size class CredDefPostQueryStringSchema(OpenAPISchema): + """Parameters and validators for query string in create credential definition.""" + credentialDefinition = fields.Nested(CredDefSchema()) options = fields.Nested(CredDefPostOptionsSchema()) @@ -116,6 +128,8 @@ class CredDefPostQueryStringSchema(OpenAPISchema): class CredDefsQueryStringSchema(OpenAPISchema): + """Parameters and validators for credential definition list query.""" + credentialDefinitionId = credentialDefinitionId issuerId = issuerId schemaId = schemaId @@ -124,21 +138,9 @@ class CredDefsQueryStringSchema(OpenAPISchema): schemaVersion = schemaVersion -""" -"primary": { - "n": "779...397", - "r": { - "link_secret": "521...922", - "": "410...200" - }, - "rctxt": "774...977", - "s": "750..893", - "z": "632...005" -} -""" - - class PrimarySchema(OpenAPISchema): + """Parameters and validators for credential definition primary.""" + n = fields.Str(example="779...397") r = fields.Dict() rctxt = fields.Str(example="774...977") @@ -147,14 +149,19 @@ class PrimarySchema(OpenAPISchema): class CredDefValueSchema(OpenAPISchema): + """Parameters and validators for credential definition value.""" + primary = fields.Nested(PrimarySchema()) class CredDefResponseSchema(OpenAPISchema): + """Parameters and validators for credential definition response.""" + issuerId = issuerId schemaId = schemaId tag = fields.Str( - description="The tag value passed in by the Issuer to an AnonCred's Credential Definition create and store implementation." + description="The tag value passed in by the Issuer to an\ + AnonCred's Credential Definition create and store implementation." ) value = fields.Nested(CredDefValueSchema()) # registration_metadata = support_revocation @@ -162,12 +169,16 @@ class CredDefResponseSchema(OpenAPISchema): class CredDefState(OpenAPISchema): + """Parameters and validators for credential definition state.""" + state = fields.Str() # TODO: create validator for only possible states credential_definition_id = credentialDefinitionId credential_definition = fields.Nested(CredDefResponseSchema()) class PostCredDefResponseSchema(OpenAPISchema): + """Parameters and validators for credential definition create response.""" + job_id = fields.Str() credential_definition_state = fields.Nested(CredDefState()) registration_metadata = fields.Dict() @@ -175,6 +186,8 @@ class PostCredDefResponseSchema(OpenAPISchema): class GetCredDefResponseSchema(OpenAPISchema): + """Parameters and validators for credential definition list response.""" + credential_definition_id = credentialDefinitionId credential_definition = fields.Nested(CredDefResponseSchema()) resolution_metadata = fields.Dict() @@ -182,55 +195,32 @@ class GetCredDefResponseSchema(OpenAPISchema): class GetCredDefsResponseSchema(OpenAPISchema): - credential_definition_id = fields.Str() - - -''' -class PublicKeysSchema(OpenAPISchema): - accumKey = fields.Dict(example='{ "z": "1 0BB...386"}') - - -class RevRegValueSchema(OpenAPISchema): - """""" - - publicKeys = PublicKeysSchema() - maxCredNum = fields.Int( - description=" The capacity of the Revocation Registry, a count of the number of credentials that can be issued using the Revocation Registry.", - example=777, - ) - tailsLocation = fields.URL( - description="The capacity of the Revocation Registry, a count of the number of credentials that can be issued using the Revocation Registry." - ) - tailsHash = fields.Str() # "91zvq2cFmBZmHCcLqFyzv7bfehHH5rMhdAG5wTjqy2PE" - - -class RevRegPostQueryStringSchema(OpenAPISchema): - """""" + """Parameters and validators for credential definition list all response.""" - issuerId = issuerId - revocDefType = fields.Str() # always "CL_ACCUM", - credDefId = INDY_CRED_DEF_ID #: "Gs6cQcvrtWoZKsbBhD3dQJ:3:CL:140384:mctc", - tag = fields.Str() - value = fields.Nested(RevRegValueSchema()) -''' + credential_definition_id = fields.Str() class SchemaState(OpenAPISchema): + """Parameters and validators for schema state.""" + state = fields.Str() # TODO: create validator for only possible states schema_id = schemaId schema = fields.Nested(SchemaSchema()) class PostSchemaResponseSchema(OpenAPISchema): + """Parameters and validators for schema state.""" + job_id = fields.Str() schema_state = fields.Nested(SchemaState()) - registration_metadata = ( - fields.Dict() - ) # For indy, schema_metadata will contain the seqNo + # For indy, schema_metadata will contain the seqNo + registration_metadata = fields.Dict() schema_metadata = fields.Dict() class SchemaResponseSchema(OpenAPISchema): + """Parameters and validators for schema create query.""" + schema = fields.Nested(SchemaSchema()) options = fields.Dict( description="Options ", @@ -242,10 +232,14 @@ class SchemaResponseSchema(OpenAPISchema): class SchemasResponseSchema(OpenAPISchema): + """Parameters and validators for schema list all response.""" + schema_id = fields.List(schemaId) class SchemasQueryStringSchema(OpenAPISchema): + """Parameters and validators for query string in schemas list query.""" + schemaName = schemaName schemaVersion = schemaVersion schemaIssuerDid = issuerId @@ -255,17 +249,60 @@ class SchemasQueryStringSchema(OpenAPISchema): @request_schema(SchemaPostQueryStringSchema()) @response_schema(PostSchemaResponseSchema(), 200, description="") async def schemas_post(request: web.BaseRequest): - context: AdminRequestContext = request["context"] - input = await request.json() - - return web.json_response({"input": input}) + """Request handler for creating a schema. + + Args: + request (web.BaseRequest): aiohttp request object + schema: { + "attrNames": ["string"], + "name": "string", + "version": "string", + "issuerId": "string" + }, + options: options method can be different per method, + but it can also include default options for all anoncreds + methods (none for schema). it can also be automatically + inferred from the agent startup parameters (default endorser) + endorser_connection_id: "" + Returns: + json object: + job_id: job identifier to keep track of the status of the schema creation. + MUST be absent or have a null value if the value of the schema_state. state + response field is either finished or failed, and MUST NOT have a null value + otherwise. + schema_state: + state : The state of the schema creation. Possible values are finished, + failed, action and wait. + schema_id : The id of the schema. If the value of the schema_state.state + response field is finished, this field MUST be present and MUST NOT have + a null value. + schema : The schema. If the value of the schema_state.state response field + is finished, this field MUST be present and MUST NOT have a null value. + registration_metadata : This field contains metadata about hte registration + process + schema_metadata : This fields contains metadata about the schema. + + """ + # context: AdminRequestContext = request["context"] + parameters = await request.json() + + return web.json_response({"input": parameters}) @docs(tags=["anoncreds"], summary="") @match_info_schema(SchemaIdMatchInfo()) @response_schema(SchemaResponseSchema(), 200, description="") async def schema_get(request: web.BaseRequest): - context: AdminRequestContext = request["context"] + """Request handler for getting a schema. + + Args: + request (web.BaseRequest): aiohttp request object + + Returns: + json object: schema + + """ + # context: AdminRequestContext = request["context"] schema_id = request.match_info["schemaId"] return web.json_response({"schema_id": schema_id}) @@ -275,7 +312,14 @@ async def schema_get(request: web.BaseRequest): @querystring_schema(SchemasQueryStringSchema()) @response_schema(SchemasResponseSchema(), 200, description="") async def schemas_get(request: web.BaseRequest): - context: AdminRequestContext = request["context"] + """Request handler for getting all schemas. + + Args: + + Returns: + + """ + # context: AdminRequestContext = request["context"] schema_issuer_did = request.query.get("schemaIssuerDid") schema_name = request.query.get("schemaName") schema_version = request.query.get("schemaVersion") @@ -293,16 +337,30 @@ async def schemas_get(request: web.BaseRequest): @request_schema(CredDefPostQueryStringSchema()) @response_schema(PostCredDefResponseSchema(), 200, description="") async def cred_def_post(request: web.BaseRequest): - context: AdminRequestContext = request["context"] - input = await request.json() - return web.json_response({"input": input}) + """Request handler for creating . + + Args: + + Returns: + + """ + # context: AdminRequestContext = request["context"] + parameters = await request.json() + return web.json_response({"input": parameters}) @docs(tags=["anoncreds"], summary="") @match_info_schema(CredIdMatchInfo()) @response_schema(GetCredDefResponseSchema(), 200, description="") async def cred_def_get(request: web.BaseRequest): - context: AdminRequestContext = request["context"] + """Request handler for getting credential definition. + + Args: + + Returns: + + """ + # context: AdminRequestContext = request["context"] credential_id = request.match_info["cred_def_id"] return web.json_response({"cred_def_id": credential_id}) @@ -311,7 +369,14 @@ async def cred_def_get(request: web.BaseRequest): @querystring_schema(CredDefsQueryStringSchema()) @response_schema(GetCredDefsResponseSchema(), 200, description="") async def cred_defs_get(request: web.BaseRequest): - context: AdminRequestContext = request["context"] + """Request handler for getting all credential definitions. + + Args: + + Returns: + + """ + # context: AdminRequestContext = request["context"] cred_def_id = request.query.get("credentialDefinitionId") issuer_id = request.query.get("issuerId") schema_id = request.query.get("schemaId") From d42dfb9a950abf5851ef709386672e1012b2f6ee Mon Sep 17 00:00:00 2001 From: Adam Burdett Date: Thu, 9 Mar 2023 10:23:47 -0700 Subject: [PATCH 028/150] Apple silicon support Signed-off-by: Adam Burdett --- demo/run_bdd | 5 +++-- scripts/run_docker | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/demo/run_bdd b/demo/run_bdd index a8306aa947..ba0329bb68 100755 --- a/demo/run_bdd +++ b/demo/run_bdd @@ -126,8 +126,8 @@ AGENT_PORT=8020 AGENT_PORT_RANGE=8020-8079 echo "Preparing agent image..." -docker build -q -t faber-alice-demo -f ../docker/Dockerfile.demo .. || exit 1 -docker build -q -t aries-bdd-image -f ../docker/Dockerfile.bdd .. || exit 1 +docker build --platform linux/amd64 -q -t faber-alice-demo -f ../docker/Dockerfile.demo .. || exit 1 +docker build --platform linux/amd64 -q -t aries-bdd-image -f ../docker/Dockerfile.bdd .. || exit 1 if [ ! -z "$DOCKERHOST" ]; then # provided via APPLICATION_URL environment variable @@ -233,6 +233,7 @@ fi DOCKER=${DOCKER:-docker} $DOCKER run --name $AGENT --rm ${DOCKER_OPTS} \ + --platform linux/amd64 \ --network=${DOCKER_NET} \ -p 0.0.0.0:$AGENT_PORT_RANGE:$AGENT_PORT_RANGE \ -v "/$(pwd)/../logs:/home/indy/logs" \ diff --git a/scripts/run_docker b/scripts/run_docker index 13b65ee2c6..ff02a85aa2 100755 --- a/scripts/run_docker +++ b/scripts/run_docker @@ -3,7 +3,7 @@ cd "$(dirname "$0")" || exit 1 CONTAINER_RUNTIME="${CONTAINER_RUNTIME:-docker}" -$CONTAINER_RUNTIME build -t aries-cloudagent-run -f ../docker/Dockerfile.run .. || exit 1 +$CONTAINER_RUNTIME build --platform linux/amd64 -t aries-cloudagent-run -f ../docker/Dockerfile.run .. || exit 1 ARGS="" for PORT in $PORTS; do @@ -89,4 +89,4 @@ fi echo "" # shellcheck disable=SC2086,SC2090 -$CONTAINER_RUNTIME run --rm -ti $ARGS aries-cloudagent-run "${ACAPY_ARGUMENTS[@]}" +$CONTAINER_RUNTIME run --rm -ti --platform linux/amd64 $ARGS aries-cloudagent-run "${ACAPY_ARGUMENTS[@]}" From 33e77fada9a0356976854b3cbac8c4ebf355d38d Mon Sep 17 00:00:00 2001 From: Char Howland Date: Fri, 10 Mar 2023 10:20:23 -0700 Subject: [PATCH 029/150] fix: file naming and structure Signed-off-by: Char Howland --- aries_cloudagent/anoncreds/anoncreds/__init__.py | 7 +++---- aries_cloudagent/anoncreds/anoncreds/base_registry.py | 2 +- .../anoncreds/{anoncreds_objects.py => models.py} | 0 .../anoncreds/did_indy_registry/definition.py | 10 ---------- .../{v1_0/did_indy_registry.py => registry.py} | 6 +++--- .../anoncreds/did_indy_registry/{v1_0 => }/routes.py | 0 .../anoncreds/did_indy_registry/v1_0/__init__.py | 0 .../anoncreds/did_web_registry/definition.py | 10 ---------- .../{v1_0/did_web_registry.py => registry.py} | 6 +++--- .../anoncreds/did_web_registry/{v1_0 => }/routes.py | 0 .../anoncreds/did_web_registry/v1_0/__init__.py | 0 11 files changed, 10 insertions(+), 31 deletions(-) rename aries_cloudagent/anoncreds/anoncreds/{anoncreds_objects.py => models.py} (100%) delete mode 100644 aries_cloudagent/anoncreds/did_indy_registry/definition.py rename aries_cloudagent/anoncreds/did_indy_registry/{v1_0/did_indy_registry.py => registry.py} (91%) rename aries_cloudagent/anoncreds/did_indy_registry/{v1_0 => }/routes.py (100%) delete mode 100644 aries_cloudagent/anoncreds/did_indy_registry/v1_0/__init__.py delete mode 100644 aries_cloudagent/anoncreds/did_web_registry/definition.py rename aries_cloudagent/anoncreds/did_web_registry/{v1_0/did_web_registry.py => registry.py} (91%) rename aries_cloudagent/anoncreds/did_web_registry/{v1_0 => }/routes.py (100%) delete mode 100644 aries_cloudagent/anoncreds/did_web_registry/v1_0/__init__.py diff --git a/aries_cloudagent/anoncreds/anoncreds/__init__.py b/aries_cloudagent/anoncreds/anoncreds/__init__.py index 117bf7ec8e..366e713119 100644 --- a/aries_cloudagent/anoncreds/anoncreds/__init__.py +++ b/aries_cloudagent/anoncreds/anoncreds/__init__.py @@ -12,18 +12,17 @@ async def setup(context: InjectionContext): """Set up default resolvers.""" registry = context.inject_or(AnonCredsRegistry) if not registry: - LOGGER.warning("No DID Resolver instance found in context") + LOGGER.warning("No AnonCredsRegistry instance found in context") return indy_registry = ClassProvider( - "aries_cloudagent.anoncreds.did_indy_registry.v1_0.did_indy_registry." - "DIDIndyRegistry" + "aries_cloudagent.anoncreds.did_indy_registry.registry.DIDIndyRegistry" ).provide(context.settings, context.injector) await indy_registry.setup(context) registry.register_registry(indy_registry) web_registry = ClassProvider( - "aries_cloudagent.anoncreds.did_web_registry.v1_0.did_web_registry.DIDWebRegistry" + "aries_cloudagent.anoncreds.did_web_registry.registry.DIDWebRegistry" ).provide(context.settings, context.injector) await web_registry.setup(context) registry.register_registry(web_registry) diff --git a/aries_cloudagent/anoncreds/anoncreds/base_registry.py b/aries_cloudagent/anoncreds/anoncreds/base_registry.py index ba8174ef5f..4e03442b9e 100644 --- a/aries_cloudagent/anoncreds/anoncreds/base_registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/base_registry.py @@ -2,7 +2,7 @@ from abc import abstractmethod from ...config.injection_context import InjectionContext -from .anoncreds_objects import ( +from .models import ( AnonCredsRegistryGetCredentialDefinition, AnonCredsRegistryGetRevocationList, AnonCredsRegistryGetRevocationRegistryDefinition, diff --git a/aries_cloudagent/anoncreds/anoncreds/anoncreds_objects.py b/aries_cloudagent/anoncreds/anoncreds/models.py similarity index 100% rename from aries_cloudagent/anoncreds/anoncreds/anoncreds_objects.py rename to aries_cloudagent/anoncreds/anoncreds/models.py diff --git a/aries_cloudagent/anoncreds/did_indy_registry/definition.py b/aries_cloudagent/anoncreds/did_indy_registry/definition.py deleted file mode 100644 index 62bddef6f5..0000000000 --- a/aries_cloudagent/anoncreds/did_indy_registry/definition.py +++ /dev/null @@ -1,10 +0,0 @@ -"""Version definitions for this protocol.""" - -versions = [ - { - "major_version": 1, - "minimum_minor_version": 0, - "current_minor_version": 0, - "path": "v1_0", - } -] diff --git a/aries_cloudagent/anoncreds/did_indy_registry/v1_0/did_indy_registry.py b/aries_cloudagent/anoncreds/did_indy_registry/registry.py similarity index 91% rename from aries_cloudagent/anoncreds/did_indy_registry/v1_0/did_indy_registry.py rename to aries_cloudagent/anoncreds/did_indy_registry/registry.py index 5363adbd5a..ab619eaed3 100644 --- a/aries_cloudagent/anoncreds/did_indy_registry/v1_0/did_indy_registry.py +++ b/aries_cloudagent/anoncreds/did_indy_registry/registry.py @@ -1,12 +1,12 @@ """DID Indy Registry""" -from ...anoncreds.anoncreds_objects import ( +from ..anoncreds.models import ( AnonCredsRegistryGetCredentialDefinition, AnonCredsRegistryGetRevocationList, AnonCredsRegistryGetRevocationRegistryDefinition, AnonCredsRegistryGetSchema, ) -from ....config.injection_context import InjectionContext -from ....anoncreds.anoncreds.anoncreds_registry import BaseRegistry +from ...config.injection_context import InjectionContext +from ..anoncreds.anoncreds_registry import BaseRegistry class DIDIndyRegistry(BaseRegistry): diff --git a/aries_cloudagent/anoncreds/did_indy_registry/v1_0/routes.py b/aries_cloudagent/anoncreds/did_indy_registry/routes.py similarity index 100% rename from aries_cloudagent/anoncreds/did_indy_registry/v1_0/routes.py rename to aries_cloudagent/anoncreds/did_indy_registry/routes.py diff --git a/aries_cloudagent/anoncreds/did_indy_registry/v1_0/__init__.py b/aries_cloudagent/anoncreds/did_indy_registry/v1_0/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/aries_cloudagent/anoncreds/did_web_registry/definition.py b/aries_cloudagent/anoncreds/did_web_registry/definition.py deleted file mode 100644 index 62bddef6f5..0000000000 --- a/aries_cloudagent/anoncreds/did_web_registry/definition.py +++ /dev/null @@ -1,10 +0,0 @@ -"""Version definitions for this protocol.""" - -versions = [ - { - "major_version": 1, - "minimum_minor_version": 0, - "current_minor_version": 0, - "path": "v1_0", - } -] diff --git a/aries_cloudagent/anoncreds/did_web_registry/v1_0/did_web_registry.py b/aries_cloudagent/anoncreds/did_web_registry/registry.py similarity index 91% rename from aries_cloudagent/anoncreds/did_web_registry/v1_0/did_web_registry.py rename to aries_cloudagent/anoncreds/did_web_registry/registry.py index 60ff7d7b92..9f38c9057a 100644 --- a/aries_cloudagent/anoncreds/did_web_registry/v1_0/did_web_registry.py +++ b/aries_cloudagent/anoncreds/did_web_registry/registry.py @@ -1,12 +1,12 @@ """DID Web Registry""" -from ...anoncreds.anoncreds_objects import ( +from ..anoncreds.models import ( AnonCredsRegistryGetCredentialDefinition, AnonCredsRegistryGetRevocationList, AnonCredsRegistryGetRevocationRegistryDefinition, AnonCredsRegistryGetSchema, ) -from ....config.injection_context import InjectionContext -from ....anoncreds.anoncreds.anoncreds_registry import BaseRegistry +from ...config.injection_context import InjectionContext +from ..anoncreds.anoncreds_registry import BaseRegistry class DIDWebRegistry(BaseRegistry): diff --git a/aries_cloudagent/anoncreds/did_web_registry/v1_0/routes.py b/aries_cloudagent/anoncreds/did_web_registry/routes.py similarity index 100% rename from aries_cloudagent/anoncreds/did_web_registry/v1_0/routes.py rename to aries_cloudagent/anoncreds/did_web_registry/routes.py diff --git a/aries_cloudagent/anoncreds/did_web_registry/v1_0/__init__.py b/aries_cloudagent/anoncreds/did_web_registry/v1_0/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 From ec4a1e393dbdb563e59dd0180e4b86d8e621781e Mon Sep 17 00:00:00 2001 From: Adam Burdett Date: Fri, 10 Mar 2023 10:46:56 -0700 Subject: [PATCH 030/150] less dry-ness Signed-off-by: Adam Burdett --- .../anoncreds/anoncreds/routes.py | 152 ++++++++++-------- 1 file changed, 89 insertions(+), 63 deletions(-) diff --git a/aries_cloudagent/anoncreds/anoncreds/routes.py b/aries_cloudagent/anoncreds/anoncreds/routes.py index ef7d9ad5cc..0dc5676bcf 100644 --- a/aries_cloudagent/anoncreds/anoncreds/routes.py +++ b/aries_cloudagent/anoncreds/anoncreds/routes.py @@ -23,17 +23,15 @@ LOGGER = logging.getLogger(__name__) -SPEC_URI = "" - -schemaId = fields.Str( - data_key="schemaId", description="Schema identifier", **INDY_SCHEMA_ID -) +SPEC_URI = "https://hyperledger.github.io/anoncreds-spec" class SchemaIdMatchInfo(OpenAPISchema): """Path parameters and validators for request taking schema id.""" - schema_id = schemaId + schema_id = fields.Str( + data_key="schemaId", description="Schema identifier", **INDY_SCHEMA_ID + ) class CredIdMatchInfo(OpenAPISchema): @@ -44,32 +42,26 @@ class CredIdMatchInfo(OpenAPISchema): ) -schemaAttrNames = fields.List( - fields.Str( - description="Attribute name", - example="score", - ), - description="Schema attribute names", - data_key="attrNames", -) -schemaName = fields.Str( - description="Schema name", - example=INDY_SCHEMA_ID["example"].split(":")[2], -) -schemaVersion = fields.Str(description="Schema version", **INDY_VERSION) -issuerId = fields.Str( - description="Issuer Identifier of the credential definition or schema", - **GENERIC_DID, -) # TODO: get correct validator - - class SchemaSchema(OpenAPISchema): """Marshmallow schema for indy schema.""" - attrNames = schemaAttrNames - name = schemaName - version = schemaVersion - issuerId = issuerId + attrNames = fields.List( + fields.Str( + description="Attribute name", + example="score", + ), + description="Schema attribute names", + data_key="attrNames", + ) + name = fields.Str( + description="Schema name", + example=INDY_SCHEMA_ID["example"].split(":")[2], + ) + version = fields.Str(description="Schema version", **INDY_VERSION) + issuerId = fields.Str( + description="Issuer Identifier of the credential definition or schema", + **GENERIC_DID, + ) # TODO: get correct validator class SchemaPostOptionSchema(OpenAPISchema): @@ -89,10 +81,6 @@ class SchemaPostQueryStringSchema(OpenAPISchema): options = fields.Nested(SchemaPostOptionSchema()) -support_revocation = fields.Bool() -revocation_registry_size = fields.Int() - - class CredDefSchema(OpenAPISchema): """Parameters and validators for credential definition.""" @@ -100,18 +88,23 @@ class CredDefSchema(OpenAPISchema): description="""The tag value passed in by the Issuer to an AnonCred's Credential Definition create and store implementation.""" ) - schemaId = schemaId - issuerId = issuerId - supportRevocation = support_revocation - revocationRegistrySize = revocation_registry_size + schemaId = fields.Str( + data_key="schemaId", description="Schema identifier", **INDY_SCHEMA_ID + ) + issuerId = fields.Str( + description="Issuer Identifier of the credential definition or schema", + **GENERIC_DID, + ) # TODO: get correct validator + supportRevocation = fields.Bool() + revocationRegistrySize = fields.Int() class CredDefPostOptionsSchema(OpenAPISchema): """Parameters and validators for credential definition options.""" endorserConnectionId = fields.Str() - supportRevocation = support_revocation - revocationRegistrySize = revocation_registry_size + supportRevocation = fields.Bool() + revocationRegistrySize = fields.Int() class CredDefPostQueryStringSchema(OpenAPISchema): @@ -121,21 +114,29 @@ class CredDefPostQueryStringSchema(OpenAPISchema): options = fields.Nested(CredDefPostOptionsSchema()) -credentialDefinitionId = fields.Str( - description="Credential definition identifier", - **INDY_CRED_DEF_ID, -) - - class CredDefsQueryStringSchema(OpenAPISchema): """Parameters and validators for credential definition list query.""" - credentialDefinitionId = credentialDefinitionId - issuerId = issuerId - schemaId = schemaId - schemaIssuerId = issuerId - schemaName = schemaName - schemaVersion = schemaVersion + credentialDefinitionId = fields.Str( + description="Credential definition identifier", + **INDY_CRED_DEF_ID, + ) + issuerId = fields.Str( + description="Issuer Identifier of the credential definition or schema", + **GENERIC_DID, + ) # TODO: get correct validator + schemaId = fields.Str( + data_key="schemaId", description="Schema identifier", **INDY_SCHEMA_ID + ) + schemaIssuerId = fields.Str( + description="Issuer Identifier of the credential definition or schema", + **GENERIC_DID, + ) # TODO: get correct validator + schemaName = fields.Str( + description="Schema name", + example=INDY_SCHEMA_ID["example"].split(":")[2], + ) + schemaVersion = fields.Str(description="Schema version", **INDY_VERSION) class PrimarySchema(OpenAPISchema): @@ -157,22 +158,30 @@ class CredDefValueSchema(OpenAPISchema): class CredDefResponseSchema(OpenAPISchema): """Parameters and validators for credential definition response.""" - issuerId = issuerId - schemaId = schemaId + issuerId = fields.Str( + description="Issuer Identifier of the credential definition or schema", + **GENERIC_DID, + ) # TODO: get correct validator + schemaId = fields.Str( + data_key="schemaId", description="Schema identifier", **INDY_SCHEMA_ID + ) tag = fields.Str( description="The tag value passed in by the Issuer to an\ AnonCred's Credential Definition create and store implementation." ) value = fields.Nested(CredDefValueSchema()) - # registration_metadata = support_revocation - # revocationRegistrySize = revocation_registry_size + # registration_metadata = fields.Bool() + # revocationRegistrySize = fields.Int() class CredDefState(OpenAPISchema): """Parameters and validators for credential definition state.""" state = fields.Str() # TODO: create validator for only possible states - credential_definition_id = credentialDefinitionId + credential_definition_id = fields.Str( + description="Credential definition identifier", + **INDY_CRED_DEF_ID, + ) credential_definition = fields.Nested(CredDefResponseSchema()) @@ -188,7 +197,10 @@ class PostCredDefResponseSchema(OpenAPISchema): class GetCredDefResponseSchema(OpenAPISchema): """Parameters and validators for credential definition list response.""" - credential_definition_id = credentialDefinitionId + credential_definition_id = fields.Str( + description="Credential definition identifier", + **INDY_CRED_DEF_ID, + ) credential_definition = fields.Nested(CredDefResponseSchema()) resolution_metadata = fields.Dict() credential_definition_metadata = fields.Dict() @@ -204,7 +216,9 @@ class SchemaState(OpenAPISchema): """Parameters and validators for schema state.""" state = fields.Str() # TODO: create validator for only possible states - schema_id = schemaId + schema_id = fields.Str( + data_key="schemaId", description="Schema identifier", **INDY_SCHEMA_ID + ) schema = fields.Nested(SchemaSchema()) @@ -226,7 +240,9 @@ class SchemaResponseSchema(OpenAPISchema): description="Options ", required=False, ) - schema_id = schemaId + schema_id = fields.Str( + data_key="schemaId", description="Schema identifier", **INDY_SCHEMA_ID + ) resolution_metadata = fields.Dict() schema_metadata = fields.Dict() @@ -234,15 +250,25 @@ class SchemaResponseSchema(OpenAPISchema): class SchemasResponseSchema(OpenAPISchema): """Parameters and validators for schema list all response.""" - schema_id = fields.List(schemaId) + schema_id = fields.List( + fields.Str( + data_key="schemaId", description="Schema identifier", **INDY_SCHEMA_ID + ) + ) class SchemasQueryStringSchema(OpenAPISchema): """Parameters and validators for query string in schemas list query.""" - schemaName = schemaName - schemaVersion = schemaVersion - schemaIssuerDid = issuerId + schemaName = fields.Str( + description="Schema name", + example=INDY_SCHEMA_ID["example"].split(":")[2], + ) + schemaVersion = fields.Str(description="Schema version", **INDY_VERSION) + schemaIssuerDid = fields.Str( + description="Issuer Identifier of the credential definition or schema", + **GENERIC_DID, + ) # TODO: get correct validator @docs(tags=["anoncreds"], summary="") From b3a6944f7faf6cf4b658e20c34770bf18ff9b906 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Fri, 10 Mar 2023 11:15:45 -0700 Subject: [PATCH 031/150] fix: AnonCredsRegistry inherits from BaseRegistry Signed-off-by: Char Howland --- .../anoncreds/anoncreds/__init__.py | 4 +- .../anoncreds/anoncreds/anoncreds_registry.py | 48 +++++++++++++++++-- .../anoncreds/anoncreds/base_registry.py | 9 +++- aries_cloudagent/config/default_context.py | 4 -- 4 files changed, 54 insertions(+), 11 deletions(-) diff --git a/aries_cloudagent/anoncreds/anoncreds/__init__.py b/aries_cloudagent/anoncreds/anoncreds/__init__.py index 366e713119..1908022f5a 100644 --- a/aries_cloudagent/anoncreds/anoncreds/__init__.py +++ b/aries_cloudagent/anoncreds/anoncreds/__init__.py @@ -16,13 +16,13 @@ async def setup(context: InjectionContext): return indy_registry = ClassProvider( - "aries_cloudagent.anoncreds.did_indy_registry.registry.DIDIndyRegistry" + "aries_cloudagent.anoncreds.did_indy_registry.registry.DIDIndyRegistry", supported_identifiers=[], method_name="did:indy", ).provide(context.settings, context.injector) await indy_registry.setup(context) registry.register_registry(indy_registry) web_registry = ClassProvider( - "aries_cloudagent.anoncreds.did_web_registry.registry.DIDWebRegistry" + "aries_cloudagent.anoncreds.did_web_registry.registry.DIDWebRegistry", supported_identifiers=[], method_name="did:web", ).provide(context.settings, context.injector) await web_registry.setup(context) registry.register_registry(web_registry) diff --git a/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py b/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py index a7902bc18b..96a1fa7615 100644 --- a/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py @@ -3,19 +3,61 @@ from typing import List from .base_registry import BaseRegistry +from .models import AnonCredsRegistryGetCredentialDefinition, AnonCredsRegistryGetRevocationList, AnonCredsRegistryGetRevocationRegistryDefinition, AnonCredsRegistryGetSchema +from ...config.injection_context import InjectionContext + LOGGER = logging.getLogger(__name__) -class AnonCredsRegistry: +class AnonCredsRegistry(BaseRegistry): """AnonCredsRegistry""" def __init__(self, registries: List[BaseRegistry] = None): """Create DID Resolver.""" + super().__init__(supported_identifiers=[], method_name="") + # TODO: need both supported_identifiers and method_name? self.registries = registries or [] - # TODO: add schema and cred def registries + + # TODO: use supported_identifier and method_name to select which registry should + # resolve or register a given object + identifier def register_registry(self, registry: BaseRegistry): """Register a new registry.""" self.registries.append(registry) - # TODO: add logic for picking the correct registry + async def setup(self, context: InjectionContext): + """Setup method.""" + + async def get_schema(self, schema_id: str) -> AnonCredsRegistryGetSchema: + """Get a schema from the registry.""" + + # TODO: determine keyword arguments + async def register_schema(self): + """Register a schema on the registry.""" + + async def get_credential_definition( + self, credential_definition_id: str + ) -> AnonCredsRegistryGetCredentialDefinition: + """Get a credential definition from the registry.""" + + # TODO: determine keyword arguments + async def register_credential_definition(self): + """Register a credential definition on the registry.""" + + async def get_revocation_registry_definition( + self, revocation_registry_id: str + ) -> AnonCredsRegistryGetRevocationRegistryDefinition: + """Get a revocation registry definition from the registry.""" + + # TODO: determine keyword arguments + async def register_revocation_registry_definition(self): + """Register a revocation registry definition on the registry.""" + + async def get_revocation_list( + self, revocation_registry_id: str, timestamp: str + ) -> AnonCredsRegistryGetRevocationList: + """Get a revocation list from the registry.""" + + # TODO: determine keyword arguments + async def register_revocation_list(self): + """Register a revocation list on the registry.""" \ No newline at end of file diff --git a/aries_cloudagent/anoncreds/anoncreds/base_registry.py b/aries_cloudagent/anoncreds/anoncreds/base_registry.py index 4e03442b9e..0ebe38456e 100644 --- a/aries_cloudagent/anoncreds/anoncreds/base_registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/base_registry.py @@ -1,5 +1,6 @@ """Base Registry""" -from abc import abstractmethod +from abc import ABC, abstractmethod +from typing import List from ...config.injection_context import InjectionContext from .models import ( @@ -10,8 +11,12 @@ ) -class BaseRegistry: +class BaseRegistry(ABC): """BaseRegistry""" + + def __init__(self, supported_identifiers: List[str], method_name: str): + """Initialize Base Registry.""" + @abstractmethod async def setup(self, context: InjectionContext): """Setup method.""" diff --git a/aries_cloudagent/config/default_context.py b/aries_cloudagent/config/default_context.py index 99cd638d76..297c1be75c 100644 --- a/aries_cloudagent/config/default_context.py +++ b/aries_cloudagent/config/default_context.py @@ -133,10 +133,6 @@ async def load_plugins(self, context: InjectionContext): plugin_registry.register_plugin("aries_cloudagent.anoncreds.did_indy_registry") plugin_registry.register_plugin("aries_cloudagent.anoncreds.did_web_registry") - # TODO: add anoncreds_plugins to argparse settings - for plugin_path in self.settings.get("anoncreds_plugins", []): - plugin_registry.register_plugin(plugin_path) - if context.settings.get("multitenant.admin_enabled"): plugin_registry.register_plugin("aries_cloudagent.multitenant.admin") From 420aba6de28532c6371121718e0f2b771a06261e Mon Sep 17 00:00:00 2001 From: Char Howland Date: Fri, 10 Mar 2023 11:20:17 -0700 Subject: [PATCH 032/150] feat: add legacy indy registry plugin structure Signed-off-by: Char Howland --- .../anoncreds/anoncreds/__init__.py | 6 +++ .../legacy_indy_registry/__init__.py | 0 .../legacy_indy_registry/registry.py | 50 +++++++++++++++++++ .../anoncreds/legacy_indy_registry/routes.py | 1 + aries_cloudagent/config/default_context.py | 1 + 5 files changed, 58 insertions(+) create mode 100644 aries_cloudagent/anoncreds/legacy_indy_registry/__init__.py create mode 100644 aries_cloudagent/anoncreds/legacy_indy_registry/registry.py create mode 100644 aries_cloudagent/anoncreds/legacy_indy_registry/routes.py diff --git a/aries_cloudagent/anoncreds/anoncreds/__init__.py b/aries_cloudagent/anoncreds/anoncreds/__init__.py index 1908022f5a..5ac21c3499 100644 --- a/aries_cloudagent/anoncreds/anoncreds/__init__.py +++ b/aries_cloudagent/anoncreds/anoncreds/__init__.py @@ -27,4 +27,10 @@ async def setup(context: InjectionContext): await web_registry.setup(context) registry.register_registry(web_registry) + legacy_indy_registry = ClassProvider( + "aries_cloudagent.anoncreds.legacy_indy_registry.registry.LegacyIndyRegistry", supported_identifiers=[], method_name="", + ).provide(context.settings, context.injector) + await legacy_indy_registry.setup(context) + registry.register_registry(legacy_indy_registry) + # TODO: add context.settings diff --git a/aries_cloudagent/anoncreds/legacy_indy_registry/__init__.py b/aries_cloudagent/anoncreds/legacy_indy_registry/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aries_cloudagent/anoncreds/legacy_indy_registry/registry.py b/aries_cloudagent/anoncreds/legacy_indy_registry/registry.py new file mode 100644 index 0000000000..5063a17aa8 --- /dev/null +++ b/aries_cloudagent/anoncreds/legacy_indy_registry/registry.py @@ -0,0 +1,50 @@ +"""Legacy Indy Registry""" +from ..anoncreds.models import ( + AnonCredsRegistryGetCredentialDefinition, + AnonCredsRegistryGetRevocationList, + AnonCredsRegistryGetRevocationRegistryDefinition, + AnonCredsRegistryGetSchema, +) +from ...config.injection_context import InjectionContext +from ..anoncreds.anoncreds_registry import BaseRegistry + + +class LegacyIndyRegistry(BaseRegistry): + """LegacyIndyRegistry""" + async def setup(self, context: InjectionContext): + """Setup.""" + print("Successfully registered LegacyIndyRegistry") + + async def get_schema(self, schema_id: str) -> AnonCredsRegistryGetSchema: + """Get a schema from the registry.""" + + # TODO: determine keyword arguments + async def register_schema(self): + """Register a schema on the registry.""" + + async def get_credential_definition( + self, credential_definition_id: str + ) -> AnonCredsRegistryGetCredentialDefinition: + """Get a credential definition from the registry.""" + + # TODO: determine keyword arguments + async def register_credential_definition(self): + """Register a credential definition on the registry.""" + + async def get_revocation_registry_definition( + self, revocation_registry_id: str + ) -> AnonCredsRegistryGetRevocationRegistryDefinition: + """Get a revocation registry definition from the registry.""" + + # TODO: determine keyword arguments + async def register_revocation_registry_definition(self): + """Register a revocation registry definition on the registry.""" + + async def get_revocation_list( + self, revocation_registry_id: str, timestamp: str + ) -> AnonCredsRegistryGetRevocationList: + """Get a revocation list from the registry.""" + + # TODO: determine keyword arguments + async def register_revocation_list(self): + """Register a revocation list on the registry.""" diff --git a/aries_cloudagent/anoncreds/legacy_indy_registry/routes.py b/aries_cloudagent/anoncreds/legacy_indy_registry/routes.py new file mode 100644 index 0000000000..def7cc9e24 --- /dev/null +++ b/aries_cloudagent/anoncreds/legacy_indy_registry/routes.py @@ -0,0 +1 @@ +"""Routes for DID Web Registry""" \ No newline at end of file diff --git a/aries_cloudagent/config/default_context.py b/aries_cloudagent/config/default_context.py index 297c1be75c..f021ce0651 100644 --- a/aries_cloudagent/config/default_context.py +++ b/aries_cloudagent/config/default_context.py @@ -132,6 +132,7 @@ async def load_plugins(self, context: InjectionContext): plugin_registry.register_plugin("aries_cloudagent.anoncreds.anoncreds") plugin_registry.register_plugin("aries_cloudagent.anoncreds.did_indy_registry") plugin_registry.register_plugin("aries_cloudagent.anoncreds.did_web_registry") + plugin_registry.register_plugin("aries_cloudagent.anoncreds.legacy_indy_registry") if context.settings.get("multitenant.admin_enabled"): plugin_registry.register_plugin("aries_cloudagent.multitenant.admin") From 9ca2d92074625dd9eb5d21510c566934756beb14 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Fri, 10 Mar 2023 11:51:02 -0700 Subject: [PATCH 033/150] feat: create default directory for registries Signed-off-by: Char Howland --- aries_cloudagent/anoncreds/anoncreds/__init__.py | 6 +++--- .../{ => anoncreds/default}/did_indy_registry/__init__.py | 0 .../{ => anoncreds/default}/did_indy_registry/registry.py | 6 +++--- .../{ => anoncreds/default}/did_indy_registry/routes.py | 0 .../{ => anoncreds/default}/did_web_registry/__init__.py | 0 .../{ => anoncreds/default}/did_web_registry/registry.py | 6 +++--- .../{ => anoncreds/default}/did_web_registry/routes.py | 0 .../default}/legacy_indy_registry/__init__.py | 0 .../default}/legacy_indy_registry/registry.py | 6 +++--- .../anoncreds/default/legacy_indy_registry/routes.py | 1 + aries_cloudagent/anoncreds/legacy_indy_registry/routes.py | 1 - aries_cloudagent/config/default_context.py | 6 +++--- 12 files changed, 16 insertions(+), 16 deletions(-) rename aries_cloudagent/anoncreds/{ => anoncreds/default}/did_indy_registry/__init__.py (100%) rename aries_cloudagent/anoncreds/{ => anoncreds/default}/did_indy_registry/registry.py (92%) rename aries_cloudagent/anoncreds/{ => anoncreds/default}/did_indy_registry/routes.py (100%) rename aries_cloudagent/anoncreds/{ => anoncreds/default}/did_web_registry/__init__.py (100%) rename aries_cloudagent/anoncreds/{ => anoncreds/default}/did_web_registry/registry.py (92%) rename aries_cloudagent/anoncreds/{ => anoncreds/default}/did_web_registry/routes.py (100%) rename aries_cloudagent/anoncreds/{ => anoncreds/default}/legacy_indy_registry/__init__.py (100%) rename aries_cloudagent/anoncreds/{ => anoncreds/default}/legacy_indy_registry/registry.py (92%) create mode 100644 aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/routes.py delete mode 100644 aries_cloudagent/anoncreds/legacy_indy_registry/routes.py diff --git a/aries_cloudagent/anoncreds/anoncreds/__init__.py b/aries_cloudagent/anoncreds/anoncreds/__init__.py index 5ac21c3499..0c461cb05c 100644 --- a/aries_cloudagent/anoncreds/anoncreds/__init__.py +++ b/aries_cloudagent/anoncreds/anoncreds/__init__.py @@ -16,19 +16,19 @@ async def setup(context: InjectionContext): return indy_registry = ClassProvider( - "aries_cloudagent.anoncreds.did_indy_registry.registry.DIDIndyRegistry", supported_identifiers=[], method_name="did:indy", + "aries_cloudagent.anoncreds.anoncreds.default.did_indy_registry.registry.DIDIndyRegistry", supported_identifiers=[], method_name="did:indy", ).provide(context.settings, context.injector) await indy_registry.setup(context) registry.register_registry(indy_registry) web_registry = ClassProvider( - "aries_cloudagent.anoncreds.did_web_registry.registry.DIDWebRegistry", supported_identifiers=[], method_name="did:web", + "aries_cloudagent.anoncreds.anoncreds.default.did_web_registry.registry.DIDWebRegistry", supported_identifiers=[], method_name="did:web", ).provide(context.settings, context.injector) await web_registry.setup(context) registry.register_registry(web_registry) legacy_indy_registry = ClassProvider( - "aries_cloudagent.anoncreds.legacy_indy_registry.registry.LegacyIndyRegistry", supported_identifiers=[], method_name="", + "aries_cloudagent.anoncreds.anoncreds.default.legacy_indy_registry.registry.LegacyIndyRegistry", supported_identifiers=[], method_name="", ).provide(context.settings, context.injector) await legacy_indy_registry.setup(context) registry.register_registry(legacy_indy_registry) diff --git a/aries_cloudagent/anoncreds/did_indy_registry/__init__.py b/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/__init__.py similarity index 100% rename from aries_cloudagent/anoncreds/did_indy_registry/__init__.py rename to aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/__init__.py diff --git a/aries_cloudagent/anoncreds/did_indy_registry/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py similarity index 92% rename from aries_cloudagent/anoncreds/did_indy_registry/registry.py rename to aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py index ab619eaed3..51009f347f 100644 --- a/aries_cloudagent/anoncreds/did_indy_registry/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py @@ -1,12 +1,12 @@ """DID Indy Registry""" -from ..anoncreds.models import ( +from ...models import ( AnonCredsRegistryGetCredentialDefinition, AnonCredsRegistryGetRevocationList, AnonCredsRegistryGetRevocationRegistryDefinition, AnonCredsRegistryGetSchema, ) -from ...config.injection_context import InjectionContext -from ..anoncreds.anoncreds_registry import BaseRegistry +from .....config.injection_context import InjectionContext +from ...anoncreds_registry import BaseRegistry class DIDIndyRegistry(BaseRegistry): diff --git a/aries_cloudagent/anoncreds/did_indy_registry/routes.py b/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/routes.py similarity index 100% rename from aries_cloudagent/anoncreds/did_indy_registry/routes.py rename to aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/routes.py diff --git a/aries_cloudagent/anoncreds/did_web_registry/__init__.py b/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/__init__.py similarity index 100% rename from aries_cloudagent/anoncreds/did_web_registry/__init__.py rename to aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/__init__.py diff --git a/aries_cloudagent/anoncreds/did_web_registry/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py similarity index 92% rename from aries_cloudagent/anoncreds/did_web_registry/registry.py rename to aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py index 9f38c9057a..d5ebd87041 100644 --- a/aries_cloudagent/anoncreds/did_web_registry/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py @@ -1,12 +1,12 @@ """DID Web Registry""" -from ..anoncreds.models import ( +from ...models import ( AnonCredsRegistryGetCredentialDefinition, AnonCredsRegistryGetRevocationList, AnonCredsRegistryGetRevocationRegistryDefinition, AnonCredsRegistryGetSchema, ) -from ...config.injection_context import InjectionContext -from ..anoncreds.anoncreds_registry import BaseRegistry +from .....config.injection_context import InjectionContext +from ...anoncreds_registry import BaseRegistry class DIDWebRegistry(BaseRegistry): diff --git a/aries_cloudagent/anoncreds/did_web_registry/routes.py b/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/routes.py similarity index 100% rename from aries_cloudagent/anoncreds/did_web_registry/routes.py rename to aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/routes.py diff --git a/aries_cloudagent/anoncreds/legacy_indy_registry/__init__.py b/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/__init__.py similarity index 100% rename from aries_cloudagent/anoncreds/legacy_indy_registry/__init__.py rename to aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/__init__.py diff --git a/aries_cloudagent/anoncreds/legacy_indy_registry/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py similarity index 92% rename from aries_cloudagent/anoncreds/legacy_indy_registry/registry.py rename to aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py index 5063a17aa8..2fe3b078de 100644 --- a/aries_cloudagent/anoncreds/legacy_indy_registry/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py @@ -1,12 +1,12 @@ """Legacy Indy Registry""" -from ..anoncreds.models import ( +from ...models import ( AnonCredsRegistryGetCredentialDefinition, AnonCredsRegistryGetRevocationList, AnonCredsRegistryGetRevocationRegistryDefinition, AnonCredsRegistryGetSchema, ) -from ...config.injection_context import InjectionContext -from ..anoncreds.anoncreds_registry import BaseRegistry +from .....config.injection_context import InjectionContext +from ...anoncreds_registry import BaseRegistry class LegacyIndyRegistry(BaseRegistry): diff --git a/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/routes.py b/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/routes.py new file mode 100644 index 0000000000..66b5eab23d --- /dev/null +++ b/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/routes.py @@ -0,0 +1 @@ +"""Routes for Legacy Indy Registry""" \ No newline at end of file diff --git a/aries_cloudagent/anoncreds/legacy_indy_registry/routes.py b/aries_cloudagent/anoncreds/legacy_indy_registry/routes.py deleted file mode 100644 index def7cc9e24..0000000000 --- a/aries_cloudagent/anoncreds/legacy_indy_registry/routes.py +++ /dev/null @@ -1 +0,0 @@ -"""Routes for DID Web Registry""" \ No newline at end of file diff --git a/aries_cloudagent/config/default_context.py b/aries_cloudagent/config/default_context.py index f021ce0651..26e1c1684d 100644 --- a/aries_cloudagent/config/default_context.py +++ b/aries_cloudagent/config/default_context.py @@ -130,9 +130,9 @@ async def load_plugins(self, context: InjectionContext): plugin_registry.register_plugin("aries_cloudagent.resolver") plugin_registry.register_plugin("aries_cloudagent.wallet") plugin_registry.register_plugin("aries_cloudagent.anoncreds.anoncreds") - plugin_registry.register_plugin("aries_cloudagent.anoncreds.did_indy_registry") - plugin_registry.register_plugin("aries_cloudagent.anoncreds.did_web_registry") - plugin_registry.register_plugin("aries_cloudagent.anoncreds.legacy_indy_registry") + plugin_registry.register_plugin("aries_cloudagent.anoncreds.anoncreds.default.did_indy_registry") + plugin_registry.register_plugin("aries_cloudagent.anoncreds.anoncreds.default.did_web_registry") + plugin_registry.register_plugin("aries_cloudagent.anoncreds.anoncreds.default.legacy_indy_registry") if context.settings.get("multitenant.admin_enabled"): plugin_registry.register_plugin("aries_cloudagent.multitenant.admin") From dc0170310f8e22cb3c13565d994af81dfdd453dc Mon Sep 17 00:00:00 2001 From: Char Howland Date: Fri, 10 Mar 2023 12:34:51 -0700 Subject: [PATCH 034/150] fix: flake8 Signed-off-by: Char Howland --- aries_cloudagent/anoncreds/anoncreds/__init__.py | 15 ++++++++++++--- .../anoncreds/anoncreds/anoncreds_registry.py | 10 ++++++++-- .../default/did_indy_registry/registry.py | 1 + .../anoncreds/default/did_indy_registry/routes.py | 2 +- .../default/did_web_registry/registry.py | 1 + .../anoncreds/default/did_web_registry/routes.py | 2 +- .../default/legacy_indy_registry/registry.py | 1 + .../default/legacy_indy_registry/routes.py | 2 +- aries_cloudagent/anoncreds/anoncreds/models.py | 11 ++++++++++- aries_cloudagent/config/default_context.py | 12 +++++++++--- 10 files changed, 45 insertions(+), 12 deletions(-) diff --git a/aries_cloudagent/anoncreds/anoncreds/__init__.py b/aries_cloudagent/anoncreds/anoncreds/__init__.py index 0c461cb05c..9506346d5d 100644 --- a/aries_cloudagent/anoncreds/anoncreds/__init__.py +++ b/aries_cloudagent/anoncreds/anoncreds/__init__.py @@ -16,19 +16,28 @@ async def setup(context: InjectionContext): return indy_registry = ClassProvider( - "aries_cloudagent.anoncreds.anoncreds.default.did_indy_registry.registry.DIDIndyRegistry", supported_identifiers=[], method_name="did:indy", + "aries_cloudagent.anoncreds.anoncreds.default.did_indy_registry.registry" + ".DIDIndyRegistry", + supported_identifiers=[], + method_name="did:indy", ).provide(context.settings, context.injector) await indy_registry.setup(context) registry.register_registry(indy_registry) web_registry = ClassProvider( - "aries_cloudagent.anoncreds.anoncreds.default.did_web_registry.registry.DIDWebRegistry", supported_identifiers=[], method_name="did:web", + "aries_cloudagent.anoncreds.anoncreds.default.did_web_registry.registry" + ".DIDWebRegistry", + supported_identifiers=[], + method_name="did:web", ).provide(context.settings, context.injector) await web_registry.setup(context) registry.register_registry(web_registry) legacy_indy_registry = ClassProvider( - "aries_cloudagent.anoncreds.anoncreds.default.legacy_indy_registry.registry.LegacyIndyRegistry", supported_identifiers=[], method_name="", + "aries_cloudagent.anoncreds.anoncreds.default.legacy_indy_registry.registry" + ".LegacyIndyRegistry", + supported_identifiers=[], + method_name="", ).provide(context.settings, context.injector) await legacy_indy_registry.setup(context) registry.register_registry(legacy_indy_registry) diff --git a/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py b/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py index 96a1fa7615..fbc82f633e 100644 --- a/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py @@ -3,7 +3,12 @@ from typing import List from .base_registry import BaseRegistry -from .models import AnonCredsRegistryGetCredentialDefinition, AnonCredsRegistryGetRevocationList, AnonCredsRegistryGetRevocationRegistryDefinition, AnonCredsRegistryGetSchema +from .models import ( + AnonCredsRegistryGetCredentialDefinition, + AnonCredsRegistryGetRevocationList, + AnonCredsRegistryGetRevocationRegistryDefinition, + AnonCredsRegistryGetSchema, +) from ...config.injection_context import InjectionContext @@ -12,6 +17,7 @@ class AnonCredsRegistry(BaseRegistry): """AnonCredsRegistry""" + def __init__(self, registries: List[BaseRegistry] = None): """Create DID Resolver.""" super().__init__(supported_identifiers=[], method_name="") @@ -60,4 +66,4 @@ async def get_revocation_list( # TODO: determine keyword arguments async def register_revocation_list(self): - """Register a revocation list on the registry.""" \ No newline at end of file + """Register a revocation list on the registry.""" diff --git a/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py index 51009f347f..68a2f729d2 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py @@ -11,6 +11,7 @@ class DIDIndyRegistry(BaseRegistry): """DIDIndyRegistry""" + async def setup(self, context: InjectionContext): """Setup.""" print("Successfully registered DIDIndyRegistry") diff --git a/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/routes.py b/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/routes.py index 9d3bc1ba85..f38bfd6623 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/routes.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/routes.py @@ -1 +1 @@ -"""Routes for DID Indy Registry""" \ No newline at end of file +"""Routes for DID Indy Registry""" diff --git a/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py index d5ebd87041..ad0749fe7d 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py @@ -11,6 +11,7 @@ class DIDWebRegistry(BaseRegistry): """DIDWebRegistry""" + async def setup(self, context: InjectionContext): """Setup.""" print("Successfully registered DIDWebRegistry") diff --git a/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/routes.py b/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/routes.py index def7cc9e24..0900c1440c 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/routes.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/routes.py @@ -1 +1 @@ -"""Routes for DID Web Registry""" \ No newline at end of file +"""Routes for DID Web Registry""" diff --git a/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py index 2fe3b078de..5badd4de1b 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py @@ -11,6 +11,7 @@ class LegacyIndyRegistry(BaseRegistry): """LegacyIndyRegistry""" + async def setup(self, context: InjectionContext): """Setup.""" print("Successfully registered LegacyIndyRegistry") diff --git a/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/routes.py b/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/routes.py index 66b5eab23d..759ee608c8 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/routes.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/routes.py @@ -1 +1 @@ -"""Routes for Legacy Indy Registry""" \ No newline at end of file +"""Routes for Legacy Indy Registry""" diff --git a/aries_cloudagent/anoncreds/anoncreds/models.py b/aries_cloudagent/anoncreds/anoncreds/models.py index c09a362cee..c034c52466 100644 --- a/aries_cloudagent/anoncreds/anoncreds/models.py +++ b/aries_cloudagent/anoncreds/anoncreds/models.py @@ -7,6 +7,7 @@ @dataclass class AnonCredsSchema: """AnonCredsSchema""" + issuerId: str attrNames: List[str] name: str @@ -16,6 +17,7 @@ class AnonCredsSchema: @dataclass class AnonCredsRegistryGetSchema: """AnonCredsRegistryGetSchema""" + schema: AnonCredsSchema schema_id: str resolution_metadata: Dict[str, Any] @@ -25,7 +27,8 @@ class AnonCredsRegistryGetSchema: # TODO: determine types for `primary` and `revocation` @dataclass class AnonCredsCredentialDefinitionValue: - """"AnonCredsCredentialDefinitionValue""" + """AnonCredsCredentialDefinitionValue""" + primary: Any revocation: Optional[Any] @@ -33,6 +36,7 @@ class AnonCredsCredentialDefinitionValue: @dataclass class AnonCredsCredentialDefinition: """AnonCredsCredentialDefinition""" + issuerId: str schemaId: str type: Literal["CL"] @@ -43,6 +47,7 @@ class AnonCredsCredentialDefinition: @dataclass class AnonCredsRegistryGetCredentialDefinition: """AnonCredsRegistryGetCredentialDefinition""" + credential_definition: AnonCredsCredentialDefinition credential_definition_id: str resolution_metadata: Dict[str, Any] @@ -52,6 +57,7 @@ class AnonCredsRegistryGetCredentialDefinition: @dataclass class AnonCredsRevocationRegistryDefinition: """AnonCredsRevocationRegistryDefinition""" + issuerId: str type: Literal["CL_ACCUM"] credDefId: str @@ -66,6 +72,7 @@ class AnonCredsRevocationRegistryDefinition: @dataclass class AnonCredsRegistryGetRevocationRegistryDefinition: """AnonCredsRegistryGetRevocationRegistryDefinition""" + revocation_registry: AnonCredsRevocationRegistryDefinition revocation_registry_id: str resolution_metadata: Dict[str, Any] @@ -75,6 +82,7 @@ class AnonCredsRegistryGetRevocationRegistryDefinition: @dataclass class AnonCredsRevocationList: """AnonCredsRevocationList""" + issuerId: str revRegId: str revocationList: List[int] @@ -85,6 +93,7 @@ class AnonCredsRevocationList: @dataclass class AnonCredsRegistryGetRevocationList: """AnonCredsRegistryGetRevocationList""" + revocation_list: AnonCredsRevocationList resolution_metadata: Dict[str, Any] revocation_registry_metadata: Dict[str, Any] diff --git a/aries_cloudagent/config/default_context.py b/aries_cloudagent/config/default_context.py index 26e1c1684d..03ba24f45c 100644 --- a/aries_cloudagent/config/default_context.py +++ b/aries_cloudagent/config/default_context.py @@ -130,9 +130,15 @@ async def load_plugins(self, context: InjectionContext): plugin_registry.register_plugin("aries_cloudagent.resolver") plugin_registry.register_plugin("aries_cloudagent.wallet") plugin_registry.register_plugin("aries_cloudagent.anoncreds.anoncreds") - plugin_registry.register_plugin("aries_cloudagent.anoncreds.anoncreds.default.did_indy_registry") - plugin_registry.register_plugin("aries_cloudagent.anoncreds.anoncreds.default.did_web_registry") - plugin_registry.register_plugin("aries_cloudagent.anoncreds.anoncreds.default.legacy_indy_registry") + plugin_registry.register_plugin( + "aries_cloudagent.anoncreds.anoncreds.default.did_indy_registry" + ) + plugin_registry.register_plugin( + "aries_cloudagent.anoncreds.anoncreds.default.did_web_registry" + ) + plugin_registry.register_plugin( + "aries_cloudagent.anoncreds.anoncreds.default.legacy_indy_registry" + ) if context.settings.get("multitenant.admin_enabled"): plugin_registry.register_plugin("aries_cloudagent.multitenant.admin") From f9de57f13ae141c6e9aba14ac07a3f893c447d49 Mon Sep 17 00:00:00 2001 From: Adam Burdett Date: Tue, 14 Mar 2023 11:50:59 -0600 Subject: [PATCH 035/150] basemodel schema bug Signed-off-by: Adam Burdett --- .../anoncreds/anoncreds/anoncreds_registry.py | 3 +- .../anoncreds/anoncreds/base_registry.py | 2 +- .../default/did_indy_registry/registry.py | 2 +- .../default/did_web_registry/registry.py | 2 +- .../default/legacy_indy_registry/registry.py | 2 +- .../anoncreds/anoncreds/models.py | 20 --- .../anoncreds/anoncreds/routes.py | 121 ++------------ .../anoncreds/models/anoncreds_cred_def.py | 31 ++++ .../anoncreds/models/anoncreds_schema.py | 151 ++++++++++++++++++ 9 files changed, 197 insertions(+), 137 deletions(-) create mode 100644 aries_cloudagent/anoncreds/models/anoncreds_cred_def.py create mode 100644 aries_cloudagent/anoncreds/models/anoncreds_schema.py diff --git a/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py b/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py index fbc82f633e..a29d89229a 100644 --- a/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py @@ -7,8 +7,9 @@ AnonCredsRegistryGetCredentialDefinition, AnonCredsRegistryGetRevocationList, AnonCredsRegistryGetRevocationRegistryDefinition, - AnonCredsRegistryGetSchema, ) +from ..models.anoncreds_schema import AnonCredsRegistryGetSchema + from ...config.injection_context import InjectionContext diff --git a/aries_cloudagent/anoncreds/anoncreds/base_registry.py b/aries_cloudagent/anoncreds/anoncreds/base_registry.py index 0ebe38456e..6df0ff3a80 100644 --- a/aries_cloudagent/anoncreds/anoncreds/base_registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/base_registry.py @@ -7,8 +7,8 @@ AnonCredsRegistryGetCredentialDefinition, AnonCredsRegistryGetRevocationList, AnonCredsRegistryGetRevocationRegistryDefinition, - AnonCredsRegistryGetSchema, ) +from ..models.anoncreds_schema import AnonCredsRegistryGetSchema class BaseRegistry(ABC): diff --git a/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py index 68a2f729d2..a9ffec826f 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py @@ -3,8 +3,8 @@ AnonCredsRegistryGetCredentialDefinition, AnonCredsRegistryGetRevocationList, AnonCredsRegistryGetRevocationRegistryDefinition, - AnonCredsRegistryGetSchema, ) +from ....models.anoncreds_schema import AnonCredsRegistryGetSchema from .....config.injection_context import InjectionContext from ...anoncreds_registry import BaseRegistry diff --git a/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py index ad0749fe7d..6e71bb92f0 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py @@ -3,8 +3,8 @@ AnonCredsRegistryGetCredentialDefinition, AnonCredsRegistryGetRevocationList, AnonCredsRegistryGetRevocationRegistryDefinition, - AnonCredsRegistryGetSchema, ) +from ....models.anoncreds_schema import AnonCredsRegistryGetSchema from .....config.injection_context import InjectionContext from ...anoncreds_registry import BaseRegistry diff --git a/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py index 5badd4de1b..3b44b048a1 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py @@ -3,8 +3,8 @@ AnonCredsRegistryGetCredentialDefinition, AnonCredsRegistryGetRevocationList, AnonCredsRegistryGetRevocationRegistryDefinition, - AnonCredsRegistryGetSchema, ) +from ....models.anoncreds_schema import AnonCredsRegistryGetSchema from .....config.injection_context import InjectionContext from ...anoncreds_registry import BaseRegistry diff --git a/aries_cloudagent/anoncreds/anoncreds/models.py b/aries_cloudagent/anoncreds/anoncreds/models.py index c034c52466..4a0703db3d 100644 --- a/aries_cloudagent/anoncreds/anoncreds/models.py +++ b/aries_cloudagent/anoncreds/anoncreds/models.py @@ -4,26 +4,6 @@ from dataclasses import dataclass -@dataclass -class AnonCredsSchema: - """AnonCredsSchema""" - - issuerId: str - attrNames: List[str] - name: str - version: str - - -@dataclass -class AnonCredsRegistryGetSchema: - """AnonCredsRegistryGetSchema""" - - schema: AnonCredsSchema - schema_id: str - resolution_metadata: Dict[str, Any] - schema_metadata: Dict[str, Any] - - # TODO: determine types for `primary` and `revocation` @dataclass class AnonCredsCredentialDefinitionValue: diff --git a/aries_cloudagent/anoncreds/anoncreds/routes.py b/aries_cloudagent/anoncreds/anoncreds/routes.py index 0dc5676bcf..846a2b5f91 100644 --- a/aries_cloudagent/anoncreds/anoncreds/routes.py +++ b/aries_cloudagent/anoncreds/anoncreds/routes.py @@ -10,6 +10,15 @@ response_schema, ) from marshmallow import fields +from aries_cloudagent.anoncreds.models.anoncreds_cred_def import CredDefValueSchema + +from aries_cloudagent.anoncreds.models.anoncreds_schema import ( + PostSchemaResponseSchema, + SchemaPostQueryStringSchema, + SchemaResponseSchema, + SchemasQueryStringSchema, + SchemasResponseSchema, +) # from ...admin.request_context import AdminRequestContext from ...messaging.models.openapi import OpenAPISchema @@ -42,43 +51,6 @@ class CredIdMatchInfo(OpenAPISchema): ) -class SchemaSchema(OpenAPISchema): - """Marshmallow schema for indy schema.""" - - attrNames = fields.List( - fields.Str( - description="Attribute name", - example="score", - ), - description="Schema attribute names", - data_key="attrNames", - ) - name = fields.Str( - description="Schema name", - example=INDY_SCHEMA_ID["example"].split(":")[2], - ) - version = fields.Str(description="Schema version", **INDY_VERSION) - issuerId = fields.Str( - description="Issuer Identifier of the credential definition or schema", - **GENERIC_DID, - ) # TODO: get correct validator - - -class SchemaPostOptionSchema(OpenAPISchema): - """Parameters and validators for schema options.""" - - endorser_connection_id = fields.UUID( - description="Connection identifier (optional)", - required=False, - example=UUIDFour.EXAMPLE, - ) - - -class SchemaPostQueryStringSchema(OpenAPISchema): - """Parameters and validators for query string in create schema.""" - - schema = fields.Nested(SchemaSchema()) - options = fields.Nested(SchemaPostOptionSchema()) class CredDefSchema(OpenAPISchema): @@ -139,22 +111,6 @@ class CredDefsQueryStringSchema(OpenAPISchema): schemaVersion = fields.Str(description="Schema version", **INDY_VERSION) -class PrimarySchema(OpenAPISchema): - """Parameters and validators for credential definition primary.""" - - n = fields.Str(example="779...397") - r = fields.Dict() - rctxt = fields.Str(example="774...977") - s = fields.Str(example="750..893") - z = fields.Str(example="632...005") - - -class CredDefValueSchema(OpenAPISchema): - """Parameters and validators for credential definition value.""" - - primary = fields.Nested(PrimarySchema()) - - class CredDefResponseSchema(OpenAPISchema): """Parameters and validators for credential definition response.""" @@ -212,65 +168,6 @@ class GetCredDefsResponseSchema(OpenAPISchema): credential_definition_id = fields.Str() -class SchemaState(OpenAPISchema): - """Parameters and validators for schema state.""" - - state = fields.Str() # TODO: create validator for only possible states - schema_id = fields.Str( - data_key="schemaId", description="Schema identifier", **INDY_SCHEMA_ID - ) - schema = fields.Nested(SchemaSchema()) - - -class PostSchemaResponseSchema(OpenAPISchema): - """Parameters and validators for schema state.""" - - job_id = fields.Str() - schema_state = fields.Nested(SchemaState()) - # For indy, schema_metadata will contain the seqNo - registration_metadata = fields.Dict() - schema_metadata = fields.Dict() - - -class SchemaResponseSchema(OpenAPISchema): - """Parameters and validators for schema create query.""" - - schema = fields.Nested(SchemaSchema()) - options = fields.Dict( - description="Options ", - required=False, - ) - schema_id = fields.Str( - data_key="schemaId", description="Schema identifier", **INDY_SCHEMA_ID - ) - resolution_metadata = fields.Dict() - schema_metadata = fields.Dict() - - -class SchemasResponseSchema(OpenAPISchema): - """Parameters and validators for schema list all response.""" - - schema_id = fields.List( - fields.Str( - data_key="schemaId", description="Schema identifier", **INDY_SCHEMA_ID - ) - ) - - -class SchemasQueryStringSchema(OpenAPISchema): - """Parameters and validators for query string in schemas list query.""" - - schemaName = fields.Str( - description="Schema name", - example=INDY_SCHEMA_ID["example"].split(":")[2], - ) - schemaVersion = fields.Str(description="Schema version", **INDY_VERSION) - schemaIssuerDid = fields.Str( - description="Issuer Identifier of the credential definition or schema", - **GENERIC_DID, - ) # TODO: get correct validator - - @docs(tags=["anoncreds"], summary="") @request_schema(SchemaPostQueryStringSchema()) @response_schema(PostSchemaResponseSchema(), 200, description="") diff --git a/aries_cloudagent/anoncreds/models/anoncreds_cred_def.py b/aries_cloudagent/anoncreds/models/anoncreds_cred_def.py new file mode 100644 index 0000000000..5f4b54aa91 --- /dev/null +++ b/aries_cloudagent/anoncreds/models/anoncreds_cred_def.py @@ -0,0 +1,31 @@ +"""Anoncreds cred def OpenAPI validators""" +from marshmallow import Schema, fields + +from aries_cloudagent.messaging.valid import NUM_STR_WHOLE + +from ...messaging.models.openapi import OpenAPISchema + + +class PrimarySchema(OpenAPISchema): + """Parameters and validators for credential definition primary.""" + + n = fields.Str(**NUM_STR_WHOLE) + s = fields.Str(**NUM_STR_WHOLE) + r = fields.Nested( + Schema.from_dict( + { + "master_secret": fields.Str(**NUM_STR_WHOLE), + "number": fields.Str(**NUM_STR_WHOLE), + "remainder": fields.Str(**NUM_STR_WHOLE), + } + ), + name="CredDefValuePrimaryRSchema", + ) + rctxt = fields.Str(**NUM_STR_WHOLE) + z = fields.Str(**NUM_STR_WHOLE) + + +class CredDefValueSchema(OpenAPISchema): + """Parameters and validators for credential definition value.""" + + primary = fields.Nested(PrimarySchema()) diff --git a/aries_cloudagent/anoncreds/models/anoncreds_schema.py b/aries_cloudagent/anoncreds/models/anoncreds_schema.py new file mode 100644 index 0000000000..775bc216cd --- /dev/null +++ b/aries_cloudagent/anoncreds/models/anoncreds_schema.py @@ -0,0 +1,151 @@ +"""Anoncreds Schema OpenAPI validators""" + +from typing import List, Dict, Any +from marshmallow import EXCLUDE, fields +from pydantic import BaseModel + +from aries_cloudagent.messaging.models.base import BaseModelSchema + +from ...messaging.models.openapi import OpenAPISchema +from ...messaging.valid import GENERIC_DID, INDY_SCHEMA_ID, INDY_VERSION, UUIDFour + + +class AnonCreds(BaseModel): + """AnonCredsSchema""" + + class Meta: + """AnonCredsSchema metadata.""" + + schema_class = "AnonCredsSchemaSchema" + + issuerId: str + attrNames: List[str] + name: str + version: str + + +class AnonCredsSchemaSchema(BaseModelSchema): + """Marshmallow schema for indy schema.""" + + class Meta: + """AnonCredsSchemaSchema metadata.""" + + model_class = AnonCreds + unknown = EXCLUDE + + issuerId = fields.Str( + description="Issuer Identifier of the credential definition or schema", + **GENERIC_DID, + ) # TODO: get correct validator + + attrNames = fields.List( + fields.Str( + description="Attribute name", + example="score", + ), + description="Schema attribute names", + data_key="attrNames", + ) + name = fields.Str( + description="Schema name", + example=INDY_SCHEMA_ID["example"].split(":")[2], + ) + version = fields.Str(description="Schema version", **INDY_VERSION) + + +class AnonCredsRegistryGetSchema(BaseModel): + """AnonCredsRegistryGetSchema""" + + class Meta: + """IndyCredInfo metadata.""" + + schema_class = "IndyCredInfoSchema" + + schema: AnonCreds + schema_id: str + resolution_metadata: Dict[str, Any] + schema_metadata: Dict[str, Any] + + +class SchemaState(OpenAPISchema): + """Parameters and validators for schema state.""" + + state = fields.Str() # TODO: create validator for only possible states + schema_id = fields.Str( + data_key="schemaId", description="Schema identifier", **INDY_SCHEMA_ID + ) + schema = fields.Nested(AnonCredsSchemaSchema()) + + +class SchemasResponseSchema(OpenAPISchema): + """Parameters and validators for schema list all response.""" + + schema_id = fields.List( + fields.Str( + data_key="schemaId", description="Schema identifier", **INDY_SCHEMA_ID + ) + ) + + +class PostSchemaResponseSchema(OpenAPISchema): + """Parameters and validators for schema state.""" + + job_id = fields.Str() + schema_state = fields.Nested(SchemaState()) + # For indy, schema_metadata will contain the seqNo + registration_metadata = fields.Dict() + schema_metadata = fields.Dict() + # TODO: no schema options? + + +class SchemaResponseSchema(OpenAPISchema): + """Parameters and validators for schema create query.""" + + class Meta: + """SchemaResponseSchema metadata.""" + + model_class = AnonCredsRegistryGetSchema + unknown = EXCLUDE + + schema = fields.Nested(AnonCredsSchemaSchema()) + schema_id = fields.Str( + data_key="schemaId", description="Schema identifier", **INDY_SCHEMA_ID + ) + # TODO: update docs to not have options + # options = fields.Dict( + # description="Options ", + # required=False, + # ) + resolution_metadata = fields.Dict() + schema_metadata = fields.Dict() + + +class SchemasQueryStringSchema(OpenAPISchema): + """Parameters and validators for query string in schemas list query.""" + + schemaName = fields.Str( + description="Schema name", + example=INDY_SCHEMA_ID["example"].split(":")[2], + ) + schemaVersion = fields.Str(description="Schema version", **INDY_VERSION) + schemaIssuerDid = fields.Str( + description="Issuer Identifier of the credential definition or schema", + **GENERIC_DID, + ) # TODO: get correct validator + + +class SchemaPostOptionSchema(OpenAPISchema): + """Parameters and validators for schema options.""" + + endorser_connection_id = fields.UUID( + description="Connection identifier (optional)", + required=False, + example=UUIDFour.EXAMPLE, + ) + + +class SchemaPostQueryStringSchema(OpenAPISchema): + """Parameters and validators for query string in create schema.""" + + schema = fields.Nested(AnonCredsSchemaSchema()) + options = fields.Nested(SchemaPostOptionSchema()) From e491f35900551e2c906854485bd6b0a8055dfa1e Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Tue, 14 Mar 2023 14:57:16 -0400 Subject: [PATCH 036/150] feat: some refinements on models Signed-off-by: Daniel Bluhm --- .../anoncreds/models/anoncreds_schema.py | 69 ++++++++++++------- 1 file changed, 46 insertions(+), 23 deletions(-) diff --git a/aries_cloudagent/anoncreds/models/anoncreds_schema.py b/aries_cloudagent/anoncreds/models/anoncreds_schema.py index 775bc216cd..13189630ea 100644 --- a/aries_cloudagent/anoncreds/models/anoncreds_schema.py +++ b/aries_cloudagent/anoncreds/models/anoncreds_schema.py @@ -10,7 +10,7 @@ from ...messaging.valid import GENERIC_DID, INDY_SCHEMA_ID, INDY_VERSION, UUIDFour -class AnonCreds(BaseModel): +class AnonCredsSchema(BaseModel): """AnonCredsSchema""" class Meta: @@ -18,10 +18,14 @@ class Meta: schema_class = "AnonCredsSchemaSchema" - issuerId: str - attrNames: List[str] - name: str - version: str + def __init__( + self, issuer_id: str, attr_names: List[str], name: str, version: str, **kwargs + ): + super().__init__(**kwargs) + self.issuer_id = issuer_id + self.attr_names = attr_names + self.name = name + self.version = version class AnonCredsSchemaSchema(BaseModelSchema): @@ -30,15 +34,14 @@ class AnonCredsSchemaSchema(BaseModelSchema): class Meta: """AnonCredsSchemaSchema metadata.""" - model_class = AnonCreds + model_class = AnonCredsSchema unknown = EXCLUDE - issuerId = fields.Str( + issuer_id = fields.Str( description="Issuer Identifier of the credential definition or schema", - **GENERIC_DID, - ) # TODO: get correct validator - - attrNames = fields.List( + data_key="issuerId", + ) + attr_names = fields.List( fields.Str( description="Attribute name", example="score", @@ -61,10 +64,36 @@ class Meta: schema_class = "IndyCredInfoSchema" - schema: AnonCreds - schema_id: str - resolution_metadata: Dict[str, Any] - schema_metadata: Dict[str, Any] + def __init__( + self, + schema: AnonCredsSchema, + schema_id: str, + resolution_metadata: Dict[str, Any], + schema_metadata: Dict[str, Any], + **kwargs + ): + super().__init__(**kwargs) + self.schema_ = schema + self.schema_id = schema_id + self.resolution_metadata = resolution_metadata + self.schema_metadata = schema_metadata + + +class AnonCredsRegistryGetSchemaSchema(BaseModelSchema): + """Parameters and validators for schema create query.""" + + class Meta: + """AnonCredsRegistryGetSchemaSchema metadata.""" + + model_class = AnonCredsRegistryGetSchema + unknown = EXCLUDE + + schema_ = fields.Nested(AnonCredsSchemaSchema(), data_key="schema") + schema_id = fields.Str( + data_key="schemaId", description="Schema identifier", **INDY_SCHEMA_ID + ) + resolution_metadata = fields.Dict() + schema_metadata = fields.Dict() class SchemaState(OpenAPISchema): @@ -74,7 +103,7 @@ class SchemaState(OpenAPISchema): schema_id = fields.Str( data_key="schemaId", description="Schema identifier", **INDY_SCHEMA_ID ) - schema = fields.Nested(AnonCredsSchemaSchema()) + schema_ = fields.Nested(AnonCredsSchemaSchema(), data_key="schema") class SchemasResponseSchema(OpenAPISchema): @@ -95,7 +124,6 @@ class PostSchemaResponseSchema(OpenAPISchema): # For indy, schema_metadata will contain the seqNo registration_metadata = fields.Dict() schema_metadata = fields.Dict() - # TODO: no schema options? class SchemaResponseSchema(OpenAPISchema): @@ -107,15 +135,10 @@ class Meta: model_class = AnonCredsRegistryGetSchema unknown = EXCLUDE - schema = fields.Nested(AnonCredsSchemaSchema()) + schema_ = fields.Nested(AnonCredsSchemaSchema(), data_key="schema") schema_id = fields.Str( data_key="schemaId", description="Schema identifier", **INDY_SCHEMA_ID ) - # TODO: update docs to not have options - # options = fields.Dict( - # description="Options ", - # required=False, - # ) resolution_metadata = fields.Dict() schema_metadata = fields.Dict() From 4d6fa86eaa53093e7d51fb08f4cd40c4c8f3472f Mon Sep 17 00:00:00 2001 From: Adam Burdett Date: Wed, 15 Mar 2023 09:15:42 -0600 Subject: [PATCH 037/150] removed old openapi schema Signed-off-by: Adam Burdett --- aries_cloudagent/anoncreds/anoncreds/routes.py | 4 ++-- .../anoncreds/models/anoncreds_schema.py | 17 ----------------- 2 files changed, 2 insertions(+), 19 deletions(-) diff --git a/aries_cloudagent/anoncreds/anoncreds/routes.py b/aries_cloudagent/anoncreds/anoncreds/routes.py index 846a2b5f91..10a8b1d6b6 100644 --- a/aries_cloudagent/anoncreds/anoncreds/routes.py +++ b/aries_cloudagent/anoncreds/anoncreds/routes.py @@ -15,7 +15,7 @@ from aries_cloudagent.anoncreds.models.anoncreds_schema import ( PostSchemaResponseSchema, SchemaPostQueryStringSchema, - SchemaResponseSchema, + AnonCredsRegistryGetSchemaSchema, SchemasQueryStringSchema, SchemasResponseSchema, ) @@ -214,7 +214,7 @@ async def schemas_post(request: web.BaseRequest): @docs(tags=["anoncreds"], summary="") @match_info_schema(SchemaIdMatchInfo()) -@response_schema(SchemaResponseSchema(), 200, description="") +@response_schema(AnonCredsRegistryGetSchemaSchema(), 200, description="") async def schema_get(request: web.BaseRequest): """Request handler for getting a schema. diff --git a/aries_cloudagent/anoncreds/models/anoncreds_schema.py b/aries_cloudagent/anoncreds/models/anoncreds_schema.py index 13189630ea..43a5c4bbb4 100644 --- a/aries_cloudagent/anoncreds/models/anoncreds_schema.py +++ b/aries_cloudagent/anoncreds/models/anoncreds_schema.py @@ -126,23 +126,6 @@ class PostSchemaResponseSchema(OpenAPISchema): schema_metadata = fields.Dict() -class SchemaResponseSchema(OpenAPISchema): - """Parameters and validators for schema create query.""" - - class Meta: - """SchemaResponseSchema metadata.""" - - model_class = AnonCredsRegistryGetSchema - unknown = EXCLUDE - - schema_ = fields.Nested(AnonCredsSchemaSchema(), data_key="schema") - schema_id = fields.Str( - data_key="schemaId", description="Schema identifier", **INDY_SCHEMA_ID - ) - resolution_metadata = fields.Dict() - schema_metadata = fields.Dict() - - class SchemasQueryStringSchema(OpenAPISchema): """Parameters and validators for query string in schemas list query.""" From c0097c7ee1a7d2d5d1402b3135e9ed8b4f601ec3 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Wed, 15 Mar 2023 12:04:41 -0400 Subject: [PATCH 038/150] feat: split interface, basic impl of core registry Signed-off-by: Daniel Bluhm --- .../anoncreds/anoncreds/anoncreds_registry.py | 117 ++++++++++++++++-- .../anoncreds/anoncreds/base_registry.py | 65 ++++++---- .../default/did_indy_registry/registry.py | 4 +- .../default/did_web_registry/registry.py | 4 +- .../default/legacy_indy_registry/registry.py | 4 +- 5 files changed, 153 insertions(+), 41 deletions(-) diff --git a/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py b/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py index fbc82f633e..6c9ab686a1 100644 --- a/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py @@ -1,69 +1,160 @@ """AnonCreds Registry""" import logging -from typing import List +from typing import List, Optional -from .base_registry import BaseRegistry +from ...config.injection_context import InjectionContext +from .base_registry import ( + AnonCredsObjectNotFound, + AnonCredsRegistrationFailed, + BaseAnonCredsError, + BaseAnonCredsHandler, + BaseAnonCredsRegistrar, + BaseAnonCredsResolver, +) from .models import ( AnonCredsRegistryGetCredentialDefinition, AnonCredsRegistryGetRevocationList, AnonCredsRegistryGetRevocationRegistryDefinition, AnonCredsRegistryGetSchema, ) -from ...config.injection_context import InjectionContext LOGGER = logging.getLogger(__name__) -class AnonCredsRegistry(BaseRegistry): +class AnonCredsRegistry(BaseAnonCredsResolver, BaseAnonCredsRegistrar): """AnonCredsRegistry""" - def __init__(self, registries: List[BaseRegistry] = None): + def __init__(self, registries: Optional[List[BaseAnonCredsHandler]] = None): """Create DID Resolver.""" - super().__init__(supported_identifiers=[], method_name="") - # TODO: need both supported_identifiers and method_name? - self.registries = registries or [] + self.resolvers = [] + self.registrars = [] + if registries: + for registry in registries: + self.register(registry) - # TODO: use supported_identifier and method_name to select which registry should - # resolve or register a given object + identifier - - def register_registry(self, registry: BaseRegistry): + def register(self, registry: BaseAnonCredsHandler): """Register a new registry.""" - self.registries.append(registry) + if isinstance(registry, BaseAnonCredsResolver): + self.resolvers.append(registry) + if isinstance(registry, BaseAnonCredsRegistrar): + self.registrars.append(registry) + + async def _resolvers_for_identifier(self, identifier: str): + return [ + resolver + for resolver in self.resolvers + if await resolver.supports(identifier) + ] + + async def _registrars_for_identifiers(self, identifier: str): + return [ + registrar + for registrar in self.registrars + if await registrar.supports(identifier) + ] async def setup(self, context: InjectionContext): """Setup method.""" async def get_schema(self, schema_id: str) -> AnonCredsRegistryGetSchema: """Get a schema from the registry.""" + for resolver in await self._resolvers_for_identifier(schema_id): + try: + return await resolver.get_schema(schema_id) + except BaseAnonCredsError: + LOGGER.exception("Error getting schema from resolver") + + raise AnonCredsObjectNotFound(f"{schema_id} could not be resolved") # TODO: determine keyword arguments async def register_schema(self): """Register a schema on the registry.""" + for registrar in await self._registrars_for_identifiers("something"): + try: + return await registrar.register_schema() + except BaseAnonCredsError: + LOGGER.exception("Error registering schema with registrar") + + raise AnonCredsRegistrationFailed("Failed to register schema") async def get_credential_definition( self, credential_definition_id: str ) -> AnonCredsRegistryGetCredentialDefinition: """Get a credential definition from the registry.""" + for resolver in await self._resolvers_for_identifier(credential_definition_id): + try: + return await resolver.get_credential_definition( + credential_definition_id + ) + except BaseAnonCredsError: + LOGGER.exception("Error getting credential definition from resolver") + + raise AnonCredsObjectNotFound( + f"{credential_definition_id} could not be resolved" + ) # TODO: determine keyword arguments async def register_credential_definition(self): """Register a credential definition on the registry.""" + for registrar in await self._registrars_for_identifiers("something"): + try: + return await registrar.register_credential_definition() + except BaseAnonCredsError: + LOGGER.exception("Error registering schema with registrar") + + raise AnonCredsRegistrationFailed("Failed to register credential definition") async def get_revocation_registry_definition( self, revocation_registry_id: str ) -> AnonCredsRegistryGetRevocationRegistryDefinition: """Get a revocation registry definition from the registry.""" + for resolver in await self._resolvers_for_identifier(revocation_registry_id): + try: + return await resolver.get_revocation_registry_definition( + revocation_registry_id + ) + except BaseAnonCredsError: + LOGGER.exception( + "Error getting revocation registry definition from resolver" + ) + + raise AnonCredsObjectNotFound(f"{revocation_registry_id} could not be resolved") # TODO: determine keyword arguments async def register_revocation_registry_definition(self): """Register a revocation registry definition on the registry.""" + for registrar in await self._registrars_for_identifiers("something"): + try: + return await registrar.register_revocation_registry_definition() + except BaseAnonCredsError: + LOGGER.exception("Error registering schema with registrar") + + raise AnonCredsRegistrationFailed( + "Failed to register revocation registry definition" + ) async def get_revocation_list( self, revocation_registry_id: str, timestamp: str ) -> AnonCredsRegistryGetRevocationList: """Get a revocation list from the registry.""" + for resolver in await self._resolvers_for_identifier(revocation_registry_id): + try: + return await resolver.get_revocation_list( + revocation_registry_id, timestamp + ) + except BaseAnonCredsError: + LOGGER.exception("Error getting revocation list from resolver") + + raise AnonCredsObjectNotFound(f"{revocation_registry_id} could not be resolved") # TODO: determine keyword arguments async def register_revocation_list(self): """Register a revocation list on the registry.""" + for registrar in await self._registrars_for_identifiers("something"): + try: + return await registrar.register_revocation_registry_definition() + except BaseAnonCredsError: + LOGGER.exception("Error registering schema with registrar") + + raise AnonCredsRegistrationFailed("Failed to register revocation list") diff --git a/aries_cloudagent/anoncreds/anoncreds/base_registry.py b/aries_cloudagent/anoncreds/anoncreds/base_registry.py index 0ebe38456e..081bd6413e 100644 --- a/aries_cloudagent/anoncreds/anoncreds/base_registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/base_registry.py @@ -1,8 +1,8 @@ """Base Registry""" -from abc import ABC, abstractmethod -from typing import List -from ...config.injection_context import InjectionContext +from abc import ABC, abstractmethod, abstractproperty +from typing import Pattern +from ...config.injection_context import InjectionContext from .models import ( AnonCredsRegistryGetCredentialDefinition, AnonCredsRegistryGetRevocationList, @@ -11,53 +11,74 @@ ) -class BaseRegistry(ABC): - """BaseRegistry""" +class BaseAnonCredsError(Exception): + """Base error class for AnonCreds.""" + + +class AnonCredsObjectNotFound(BaseAnonCredsError): + """Raised when object is not found in resolver.""" + + +class AnonCredsRegistrationFailed(BaseAnonCredsError): + """Raised when registering an AnonCreds object fails.""" - def __init__(self, supported_identifiers: List[str], method_name: str): - """Initialize Base Registry.""" + +class BaseAnonCredsHandler(ABC): + @abstractproperty + def supported_identifiers_regex(self) -> Pattern: + """Regex to match supported identifiers.""" + ... + + async def supports(self, identifier: str) -> bool: + """Determine whether this registry supports the given identifier.""" + return bool(self.supported_identifiers_regex.match(identifier)) @abstractmethod async def setup(self, context: InjectionContext): """Setup method.""" + +class BaseAnonCredsResolver(BaseAnonCredsHandler): @abstractmethod async def get_schema(self, schema_id: str) -> AnonCredsRegistryGetSchema: """Get a schema from the registry.""" - # TODO: determine keyword arguments - @abstractmethod - async def register_schema(self): - """Register a schema on the registry.""" - @abstractmethod async def get_credential_definition( self, credential_definition_id: str ) -> AnonCredsRegistryGetCredentialDefinition: """Get a credential definition from the registry.""" - # TODO: determine keyword arguments - @abstractmethod - async def register_credential_definition(self): - """Register a credential definition on the registry.""" - @abstractmethod async def get_revocation_registry_definition( self, revocation_registry_id: str ) -> AnonCredsRegistryGetRevocationRegistryDefinition: """Get a revocation registry definition from the registry.""" - # TODO: determine keyword arguments - @abstractmethod - async def register_revocation_registry_definition(self): - """Register a revocation registry definition on the registry.""" - @abstractmethod async def get_revocation_list( self, revocation_registry_id: str, timestamp: str ) -> AnonCredsRegistryGetRevocationList: """Get a revocation list from the registry.""" + +class BaseAnonCredsRegistrar(BaseAnonCredsHandler): + + # TODO: determine keyword arguments + @abstractmethod + async def register_schema(self): + """Register a schema on the registry.""" + + # TODO: determine keyword arguments + @abstractmethod + async def register_credential_definition(self): + """Register a credential definition on the registry.""" + + # TODO: determine keyword arguments + @abstractmethod + async def register_revocation_registry_definition(self): + """Register a revocation registry definition on the registry.""" + # TODO: determine keyword arguments @abstractmethod async def register_revocation_list(self): diff --git a/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py index 68a2f729d2..93847657a0 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py @@ -6,10 +6,10 @@ AnonCredsRegistryGetSchema, ) from .....config.injection_context import InjectionContext -from ...anoncreds_registry import BaseRegistry +from ...base_registry import BaseAnonCredsResolver, BaseAnonCredsRegistrar -class DIDIndyRegistry(BaseRegistry): +class DIDIndyRegistry(BaseAnonCredsResolver, BaseAnonCredsRegistrar): """DIDIndyRegistry""" async def setup(self, context: InjectionContext): diff --git a/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py index ad0749fe7d..cb361f01ca 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py @@ -6,10 +6,10 @@ AnonCredsRegistryGetSchema, ) from .....config.injection_context import InjectionContext -from ...anoncreds_registry import BaseRegistry +from ...base_registry import BaseAnonCredsResolver, BaseAnonCredsRegistrar -class DIDWebRegistry(BaseRegistry): +class DIDWebRegistry(BaseAnonCredsResolver, BaseAnonCredsRegistrar): """DIDWebRegistry""" async def setup(self, context: InjectionContext): diff --git a/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py index 5badd4de1b..96f129d466 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py @@ -6,10 +6,10 @@ AnonCredsRegistryGetSchema, ) from .....config.injection_context import InjectionContext -from ...anoncreds_registry import BaseRegistry +from ...base_registry import BaseAnonCredsResolver, BaseAnonCredsRegistrar -class LegacyIndyRegistry(BaseRegistry): +class LegacyIndyRegistry(BaseAnonCredsResolver, BaseAnonCredsRegistrar): """LegacyIndyRegistry""" async def setup(self, context: InjectionContext): From b7df8258fa56cf83a0fc5505421aea0a494905a4 Mon Sep 17 00:00:00 2001 From: Adam Burdett Date: Wed, 15 Mar 2023 11:17:59 -0600 Subject: [PATCH 039/150] get schema admin api, injection. Signed-off-by: Adam Burdett --- aries_cloudagent/anoncreds/anoncreds/routes.py | 9 ++++++--- aries_cloudagent/anoncreds/models/anoncreds_schema.py | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/aries_cloudagent/anoncreds/anoncreds/routes.py b/aries_cloudagent/anoncreds/anoncreds/routes.py index 10a8b1d6b6..370af47199 100644 --- a/aries_cloudagent/anoncreds/anoncreds/routes.py +++ b/aries_cloudagent/anoncreds/anoncreds/routes.py @@ -10,6 +10,7 @@ response_schema, ) from marshmallow import fields +from aries_cloudagent.anoncreds.anoncreds.anoncreds_registry import AnonCredsRegistry from aries_cloudagent.anoncreds.models.anoncreds_cred_def import CredDefValueSchema from aries_cloudagent.anoncreds.models.anoncreds_schema import ( @@ -20,7 +21,7 @@ SchemasResponseSchema, ) -# from ...admin.request_context import AdminRequestContext +from ...admin.request_context import AdminRequestContext from ...messaging.models.openapi import OpenAPISchema from ...messaging.valid import ( GENERIC_DID, @@ -225,10 +226,12 @@ async def schema_get(request: web.BaseRequest): json object: schema """ - # context: AdminRequestContext = request["context"] + context: AdminRequestContext = request["context"] + anon_creds_registry = context.inject(AnonCredsRegistry) schema_id = request.match_info["schemaId"] + result = await anon_creds_registry.get_schema(schema_id) - return web.json_response({"schema_id": schema_id}) + return web.json_response(result) @docs(tags=["anoncreds"], summary="") diff --git a/aries_cloudagent/anoncreds/models/anoncreds_schema.py b/aries_cloudagent/anoncreds/models/anoncreds_schema.py index 43a5c4bbb4..1adbdb37c1 100644 --- a/aries_cloudagent/anoncreds/models/anoncreds_schema.py +++ b/aries_cloudagent/anoncreds/models/anoncreds_schema.py @@ -62,7 +62,7 @@ class AnonCredsRegistryGetSchema(BaseModel): class Meta: """IndyCredInfo metadata.""" - schema_class = "IndyCredInfoSchema" + schema_class = "AnonCredsRegistryGetSchemaSchema" def __init__( self, From 3eb0336ef74591de8e6aea9e006b2e83658005da Mon Sep 17 00:00:00 2001 From: Adam Burdett Date: Wed, 15 Mar 2023 11:37:44 -0600 Subject: [PATCH 040/150] more model work Signed-off-by: Adam Burdett --- .../anoncreds/anoncreds/models.py | 54 ------ .../anoncreds/anoncreds/routes.py | 116 +----------- .../anoncreds/models/anoncreds_cred_def.py | 169 +++++++++++++++++- .../anoncreds/models/anoncreds_schema.py | 13 +- .../anoncreds/models/anoncreds_valid.py | 35 ++++ 5 files changed, 212 insertions(+), 175 deletions(-) create mode 100644 aries_cloudagent/anoncreds/models/anoncreds_valid.py diff --git a/aries_cloudagent/anoncreds/anoncreds/models.py b/aries_cloudagent/anoncreds/anoncreds/models.py index 4a0703db3d..1c87c730ce 100644 --- a/aries_cloudagent/anoncreds/anoncreds/models.py +++ b/aries_cloudagent/anoncreds/anoncreds/models.py @@ -4,60 +4,6 @@ from dataclasses import dataclass -# TODO: determine types for `primary` and `revocation` -@dataclass -class AnonCredsCredentialDefinitionValue: - """AnonCredsCredentialDefinitionValue""" - - primary: Any - revocation: Optional[Any] - - -@dataclass -class AnonCredsCredentialDefinition: - """AnonCredsCredentialDefinition""" - - issuerId: str - schemaId: str - type: Literal["CL"] - tag: str - value: AnonCredsCredentialDefinitionValue - - -@dataclass -class AnonCredsRegistryGetCredentialDefinition: - """AnonCredsRegistryGetCredentialDefinition""" - - credential_definition: AnonCredsCredentialDefinition - credential_definition_id: str - resolution_metadata: Dict[str, Any] - credential_definition_metadata: Dict[str, Any] - - -@dataclass -class AnonCredsRevocationRegistryDefinition: - """AnonCredsRevocationRegistryDefinition""" - - issuerId: str - type: Literal["CL_ACCUM"] - credDefId: str - tag: str - # TODO: determine type for `publicKeys` - publicKeys: Any - maxCredNum: int - tailsLocation: str - tailsHash: str - - -@dataclass -class AnonCredsRegistryGetRevocationRegistryDefinition: - """AnonCredsRegistryGetRevocationRegistryDefinition""" - - revocation_registry: AnonCredsRevocationRegistryDefinition - revocation_registry_id: str - resolution_metadata: Dict[str, Any] - revocation_registry_metadata: Dict[str, Any] - @dataclass class AnonCredsRevocationList: diff --git a/aries_cloudagent/anoncreds/anoncreds/routes.py b/aries_cloudagent/anoncreds/anoncreds/routes.py index 370af47199..d4eedf482a 100644 --- a/aries_cloudagent/anoncreds/anoncreds/routes.py +++ b/aries_cloudagent/anoncreds/anoncreds/routes.py @@ -11,7 +11,7 @@ ) from marshmallow import fields from aries_cloudagent.anoncreds.anoncreds.anoncreds_registry import AnonCredsRegistry -from aries_cloudagent.anoncreds.models.anoncreds_cred_def import CredDefValueSchema +from aries_cloudagent.anoncreds.models.anoncreds_cred_def import CredDefPostQueryStringSchema, CredDefValueSchema, CredDefsQueryStringSchema, GetCredDefResponseSchema, GetCredDefsResponseSchema, PostCredDefResponseSchema from aries_cloudagent.anoncreds.models.anoncreds_schema import ( PostSchemaResponseSchema, @@ -54,120 +54,6 @@ class CredIdMatchInfo(OpenAPISchema): -class CredDefSchema(OpenAPISchema): - """Parameters and validators for credential definition.""" - - tag = fields.Str( - description="""The tag value passed in by the Issuer to - an AnonCred's Credential Definition create and store implementation.""" - ) - schemaId = fields.Str( - data_key="schemaId", description="Schema identifier", **INDY_SCHEMA_ID - ) - issuerId = fields.Str( - description="Issuer Identifier of the credential definition or schema", - **GENERIC_DID, - ) # TODO: get correct validator - supportRevocation = fields.Bool() - revocationRegistrySize = fields.Int() - - -class CredDefPostOptionsSchema(OpenAPISchema): - """Parameters and validators for credential definition options.""" - - endorserConnectionId = fields.Str() - supportRevocation = fields.Bool() - revocationRegistrySize = fields.Int() - - -class CredDefPostQueryStringSchema(OpenAPISchema): - """Parameters and validators for query string in create credential definition.""" - - credentialDefinition = fields.Nested(CredDefSchema()) - options = fields.Nested(CredDefPostOptionsSchema()) - - -class CredDefsQueryStringSchema(OpenAPISchema): - """Parameters and validators for credential definition list query.""" - - credentialDefinitionId = fields.Str( - description="Credential definition identifier", - **INDY_CRED_DEF_ID, - ) - issuerId = fields.Str( - description="Issuer Identifier of the credential definition or schema", - **GENERIC_DID, - ) # TODO: get correct validator - schemaId = fields.Str( - data_key="schemaId", description="Schema identifier", **INDY_SCHEMA_ID - ) - schemaIssuerId = fields.Str( - description="Issuer Identifier of the credential definition or schema", - **GENERIC_DID, - ) # TODO: get correct validator - schemaName = fields.Str( - description="Schema name", - example=INDY_SCHEMA_ID["example"].split(":")[2], - ) - schemaVersion = fields.Str(description="Schema version", **INDY_VERSION) - - -class CredDefResponseSchema(OpenAPISchema): - """Parameters and validators for credential definition response.""" - - issuerId = fields.Str( - description="Issuer Identifier of the credential definition or schema", - **GENERIC_DID, - ) # TODO: get correct validator - schemaId = fields.Str( - data_key="schemaId", description="Schema identifier", **INDY_SCHEMA_ID - ) - tag = fields.Str( - description="The tag value passed in by the Issuer to an\ - AnonCred's Credential Definition create and store implementation." - ) - value = fields.Nested(CredDefValueSchema()) - # registration_metadata = fields.Bool() - # revocationRegistrySize = fields.Int() - - -class CredDefState(OpenAPISchema): - """Parameters and validators for credential definition state.""" - - state = fields.Str() # TODO: create validator for only possible states - credential_definition_id = fields.Str( - description="Credential definition identifier", - **INDY_CRED_DEF_ID, - ) - credential_definition = fields.Nested(CredDefResponseSchema()) - - -class PostCredDefResponseSchema(OpenAPISchema): - """Parameters and validators for credential definition create response.""" - - job_id = fields.Str() - credential_definition_state = fields.Nested(CredDefState()) - registration_metadata = fields.Dict() - credential_definition_metadata = fields.Dict() - - -class GetCredDefResponseSchema(OpenAPISchema): - """Parameters and validators for credential definition list response.""" - - credential_definition_id = fields.Str( - description="Credential definition identifier", - **INDY_CRED_DEF_ID, - ) - credential_definition = fields.Nested(CredDefResponseSchema()) - resolution_metadata = fields.Dict() - credential_definition_metadata = fields.Dict() - - -class GetCredDefsResponseSchema(OpenAPISchema): - """Parameters and validators for credential definition list all response.""" - - credential_definition_id = fields.Str() - @docs(tags=["anoncreds"], summary="") @request_schema(SchemaPostQueryStringSchema()) diff --git a/aries_cloudagent/anoncreds/models/anoncreds_cred_def.py b/aries_cloudagent/anoncreds/models/anoncreds_cred_def.py index 5f4b54aa91..9531d5ba3a 100644 --- a/aries_cloudagent/anoncreds/models/anoncreds_cred_def.py +++ b/aries_cloudagent/anoncreds/models/anoncreds_cred_def.py @@ -1,11 +1,62 @@ """Anoncreds cred def OpenAPI validators""" from marshmallow import Schema, fields +from aries_cloudagent.anoncreds.models.anoncreds_valid import ANONCREDS_SCHEMA_ID,ANONCREDS_VERSION -from aries_cloudagent.messaging.valid import NUM_STR_WHOLE +from aries_cloudagent.messaging.valid import GENERIC_DID, INDY_CRED_DEF_ID, NUM_STR_WHOLE from ...messaging.models.openapi import OpenAPISchema +# TODO: determine types for `primary` and `revocation` +class AnonCredsCredentialDefinitionValue: + """AnonCredsCredentialDefinitionValue""" + + primary: Any + revocation: Optional[Any] + + + +class AnonCredsCredentialDefinition: + """AnonCredsCredentialDefinition""" + + issuerId: str + schemaId: str + type: Literal["CL"] + tag: str + value: AnonCredsCredentialDefinitionValue + + +class AnonCredsRegistryGetCredentialDefinition: + """AnonCredsRegistryGetCredentialDefinition""" + + credential_definition: AnonCredsCredentialDefinition + credential_definition_id: str + resolution_metadata: Dict[str, Any] + credential_definition_metadata: Dict[str, Any] + + +class AnonCredsRevocationRegistryDefinition: + """AnonCredsRevocationRegistryDefinition""" + + issuerId: str + type: Literal["CL_ACCUM"] + credDefId: str + tag: str + # TODO: determine type for `publicKeys` + publicKeys: Any + maxCredNum: int + tailsLocation: str + tailsHash: str + + +class AnonCredsRegistryGetRevocationRegistryDefinition: + """AnonCredsRegistryGetRevocationRegistryDefinition""" + + revocation_registry: AnonCredsRevocationRegistryDefinition + revocation_registry_id: str + resolution_metadata: Dict[str, Any] + revocation_registry_metadata: Dict[str, Any] + class PrimarySchema(OpenAPISchema): """Parameters and validators for credential definition primary.""" @@ -29,3 +80,119 @@ class CredDefValueSchema(OpenAPISchema): """Parameters and validators for credential definition value.""" primary = fields.Nested(PrimarySchema()) + + + +class CredDefSchema(OpenAPISchema): + """Parameters and validators for credential definition.""" + + tag = fields.Str( + description="""The tag value passed in by the Issuer to + an AnonCred's Credential Definition create and store implementation.""" + ) + schemaId = fields.Str( + data_key="schemaId", description="Schema identifier", **ANONCREDS_SCHEMA_ID + ) + issuerId = fields.Str( + description="Issuer Identifier of the credential definition or schema", + **GENERIC_DID, + ) # TODO: get correct validator + supportRevocation = fields.Bool() + revocationRegistrySize = fields.Int() + + +class CredDefPostOptionsSchema(OpenAPISchema): + """Parameters and validators for credential definition options.""" + + endorserConnectionId = fields.Str() + supportRevocation = fields.Bool() + revocationRegistrySize = fields.Int() + + +class CredDefPostQueryStringSchema(OpenAPISchema): + """Parameters and validators for query string in create credential definition.""" + + credentialDefinition = fields.Nested(CredDefSchema()) + options = fields.Nested(CredDefPostOptionsSchema()) + + +class CredDefsQueryStringSchema(OpenAPISchema): + """Parameters and validators for credential definition list query.""" + + credentialDefinitionId = fields.Str( + description="Credential definition identifier", + **INDY_CRED_DEF_ID, + ) + issuerId = fields.Str( + description="Issuer Identifier of the credential definition or schema", + **GENERIC_DID, + ) # TODO: get correct validator + schemaId = fields.Str( + data_key="schemaId", description="Schema identifier", **ANONCREDS_SCHEMA_ID + ) + schemaIssuerId = fields.Str( + description="Issuer Identifier of the credential definition or schema", + **GENERIC_DID, + ) # TODO: get correct validator + schemaName = fields.Str( + description="Schema name", + example=ANONCREDS_SCHEMA_ID["example"].split(":")[2], + ) + schemaVersion = fields.Str(description="Schema version", **ANONCREDS_VERSION) + + +class CredDefResponseSchema(OpenAPISchema): + """Parameters and validators for credential definition response.""" + + issuerId = fields.Str( + description="Issuer Identifier of the credential definition or schema", + **GENERIC_DID, + ) # TODO: get correct validator + schemaId = fields.Str( + data_key="schemaId", description="Schema identifier", **ANONCREDS_SCHEMA_ID + ) + tag = fields.Str( + description="The tag value passed in by the Issuer to an\ + AnonCred's Credential Definition create and store implementation." + ) + value = fields.Nested(CredDefValueSchema()) + # registration_metadata = fields.Bool() + # revocationRegistrySize = fields.Int() + + +class CredDefState(OpenAPISchema): + """Parameters and validators for credential definition state.""" + + state = fields.Str() # TODO: create validator for only possible states + credential_definition_id = fields.Str( + description="Credential definition identifier", + **INDY_CRED_DEF_ID, + ) + credential_definition = fields.Nested(CredDefResponseSchema()) + + +class PostCredDefResponseSchema(OpenAPISchema): + """Parameters and validators for credential definition create response.""" + + job_id = fields.Str() + credential_definition_state = fields.Nested(CredDefState()) + registration_metadata = fields.Dict() + credential_definition_metadata = fields.Dict() + + +class GetCredDefResponseSchema(OpenAPISchema): + """Parameters and validators for credential definition list response.""" + + credential_definition_id = fields.Str( + description="Credential definition identifier", + **INDY_CRED_DEF_ID, + ) + credential_definition = fields.Nested(CredDefResponseSchema()) + resolution_metadata = fields.Dict() + credential_definition_metadata = fields.Dict() + + +class GetCredDefsResponseSchema(OpenAPISchema): + """Parameters and validators for credential definition list all response.""" + + credential_definition_id = fields.Str() \ No newline at end of file diff --git a/aries_cloudagent/anoncreds/models/anoncreds_schema.py b/aries_cloudagent/anoncreds/models/anoncreds_schema.py index 1adbdb37c1..86c3f86fc9 100644 --- a/aries_cloudagent/anoncreds/models/anoncreds_schema.py +++ b/aries_cloudagent/anoncreds/models/anoncreds_schema.py @@ -1,13 +1,16 @@ """Anoncreds Schema OpenAPI validators""" -from typing import List, Dict, Any +from typing import Any, Dict, List + from marshmallow import EXCLUDE, fields from pydantic import BaseModel +from aries_cloudagent.anoncreds.models.anoncreds_valid import ( + ANONCREDS_SCHEMA_ID, ANONCREDS_VERSION) from aries_cloudagent.messaging.models.base import BaseModelSchema from ...messaging.models.openapi import OpenAPISchema -from ...messaging.valid import GENERIC_DID, INDY_SCHEMA_ID, INDY_VERSION, UUIDFour +from ...messaging.valid import GENERIC_DID, UUIDFour class AnonCredsSchema(BaseModel): @@ -51,9 +54,9 @@ class Meta: ) name = fields.Str( description="Schema name", - example=INDY_SCHEMA_ID["example"].split(":")[2], + example=ANONCREDS_SCHEMA_ID["example"].split(":")[2], ) - version = fields.Str(description="Schema version", **INDY_VERSION) + version = fields.Str(description="Schema version", **ANONCREDS_VERSION) class AnonCredsRegistryGetSchema(BaseModel): @@ -90,7 +93,7 @@ class Meta: schema_ = fields.Nested(AnonCredsSchemaSchema(), data_key="schema") schema_id = fields.Str( - data_key="schemaId", description="Schema identifier", **INDY_SCHEMA_ID + data_key="schemaId", description="Schema identifier", **ANONCREDS_SCHEMA_ID ) resolution_metadata = fields.Dict() schema_metadata = fields.Dict() diff --git a/aries_cloudagent/anoncreds/models/anoncreds_valid.py b/aries_cloudagent/anoncreds/models/anoncreds_valid.py new file mode 100644 index 0000000000..681af03434 --- /dev/null +++ b/aries_cloudagent/anoncreds/models/anoncreds_valid.py @@ -0,0 +1,35 @@ +from base58 import alphabet +from marshmallow.validate import OneOf, Range, Regexp, Validator + +B58 = alphabet if isinstance(alphabet, str) else alphabet.decode("ascii") + +class AnonCredsSchemaId(Regexp): + """Validate value against indy schema identifier specification.""" + + EXAMPLE = "WgWxqztrNooG92RXvxSTWv:2:schema_name:1.0" + PATTERN = rf"^[{B58}]{{21,22}}:2:.+:[0-9.]+$" + + def __init__(self): + """Initializer.""" + + super().__init__( + AnonCredsSchemaId.PATTERN, + error="Value {input} is not an indy schema identifier", + ) + +class AnonCredsVersion(Regexp): + """Validate value against indy version specification.""" + + EXAMPLE = "1.0" + PATTERN = r"^[0-9.]+$" + + def __init__(self): + """Initializer.""" + + super().__init__( + AnonCredsVersion.PATTERN, + error="Value {input} is not an indy version (use only digits and '.')", + ) + +ANONCREDS_SCHEMA_ID = {"validate": AnonCredsSchemaId(), "example": AnonCredsSchemaId.EXAMPLE} +ANONCREDS_VERSION = {"validate": AnonCredsVersion(), "example": AnonCredsVersion.EXAMPLE} From 3cab55ff07bf6f355ea4f815f02a9ad5b151628b Mon Sep 17 00:00:00 2001 From: Adam Burdett Date: Wed, 15 Mar 2023 15:16:20 -0600 Subject: [PATCH 041/150] cred_def models Signed-off-by: Adam Burdett --- .../anoncreds/anoncreds/routes.py | 112 ++++++- .../anoncreds/models/anoncreds_cred_def.py | 293 ++++++++++-------- .../anoncreds/models/anoncreds_schema.py | 11 +- 3 files changed, 276 insertions(+), 140 deletions(-) diff --git a/aries_cloudagent/anoncreds/anoncreds/routes.py b/aries_cloudagent/anoncreds/anoncreds/routes.py index d4eedf482a..002e805473 100644 --- a/aries_cloudagent/anoncreds/anoncreds/routes.py +++ b/aries_cloudagent/anoncreds/anoncreds/routes.py @@ -11,7 +11,14 @@ ) from marshmallow import fields from aries_cloudagent.anoncreds.anoncreds.anoncreds_registry import AnonCredsRegistry -from aries_cloudagent.anoncreds.models.anoncreds_cred_def import CredDefPostQueryStringSchema, CredDefValueSchema, CredDefsQueryStringSchema, GetCredDefResponseSchema, GetCredDefsResponseSchema, PostCredDefResponseSchema +from aries_cloudagent.anoncreds.models.anoncreds_cred_def import ( + CredDefPostQueryStringSchema, + AnonCredsCredentialDefinitionValueSchema, + CredDefsQueryStringSchema, + AnonCredsRegistryGetCredentialDefinitionSchema, + GetCredDefsResponseSchema, + PostCredDefResponseSchema, +) from aries_cloudagent.anoncreds.models.anoncreds_schema import ( PostSchemaResponseSchema, @@ -52,8 +59,97 @@ class CredIdMatchInfo(OpenAPISchema): ) +class CredDefSchema(OpenAPISchema): + """Parameters and validators for credential definition.""" + + tag = fields.Str( + description="""The tag value passed in by the Issuer to + an AnonCred's Credential Definition create and store implementation.""" + ) + schemaId = fields.Str( + data_key="schemaId", description="Schema identifier", **ANONCREDS_SCHEMA_ID + ) + issuerId = fields.Str( + description="Issuer Identifier of the credential definition or schema", + **GENERIC_DID, + ) # TODO: get correct validator + supportRevocation = fields.Bool() + revocationRegistrySize = fields.Int() + + +class CredDefPostOptionsSchema(OpenAPISchema): + """Parameters and validators for credential definition options.""" + + endorserConnectionId = fields.Str() + supportRevocation = fields.Bool() + revocationRegistrySize = fields.Int() + +class CredDefPostQueryStringSchema(OpenAPISchema): + """Parameters and validators for query string in create credential definition.""" + credentialDefinition = fields.Nested(CredDefSchema()) + options = fields.Nested(CredDefPostOptionsSchema()) + + +class CredDefsQueryStringSchema(OpenAPISchema): + """Parameters and validators for credential definition list query.""" + + credentialDefinitionId = fields.Str( + description="Credential definition identifier", + **INDY_CRED_DEF_ID, + data_key="credentialDefinitionId", + ) + issuer_id = fields.Str( + description="Issuer Identifier of the credential definition or schema", + **GENERIC_DID, + data_key="issuerId", + ) # TODO: get correct validator + schema_id = fields.Str( + data_key="schemaId", description="Schema identifier", **ANONCREDS_SCHEMA_ID + ) + schema_issuer_id = fields.Str( + description="Issuer Identifier of the credential definition or schema", + **GENERIC_DID, + data_key="schemaIssuerId", + ) # TODO: get correct validator + schema_name = fields.Str( + description="Schema name", + example=ANONCREDS_SCHEMA_ID["example"].split(":")[2], + data_key="schemaName", + ) + schema_version = fields.Str( + description="Schema version", **ANONCREDS_VERSION, data_key="schemaVersion" + ) + + +class CredDefState(OpenAPISchema): + """Parameters and validators for credential definition state.""" + + state = fields.Str() # TODO: create validator for only possible states + credential_definition_id = fields.Str( + description="Credential definition identifier", + **INDY_CRED_DEF_ID, + ) + credential_definition = fields.Nested(AnonCredsCredentialDefinitionSchema()) + + +class PostCredDefResponseSchema(OpenAPISchema): + """Parameters and validators for credential definition create response.""" + + job_id = fields.Str() + credential_definition_state = fields.Nested(CredDefState()) + registration_metadata = fields.Dict() + credential_definition_metadata = fields.Dict() + + + + + +class GetCredDefsResponseSchema(OpenAPISchema): + """Parameters and validators for credential definition list all response.""" + + credential_definition_id = fields.Str() @docs(tags=["anoncreds"], summary="") @request_schema(SchemaPostQueryStringSchema()) @@ -163,7 +259,7 @@ async def cred_def_post(request: web.BaseRequest): @docs(tags=["anoncreds"], summary="") @match_info_schema(CredIdMatchInfo()) -@response_schema(GetCredDefResponseSchema(), 200, description="") +@response_schema(AnonCredsRegistryGetCredentialDefinitionSchema(), 200, description="") async def cred_def_get(request: web.BaseRequest): """Request handler for getting credential definition. @@ -189,12 +285,12 @@ async def cred_defs_get(request: web.BaseRequest): """ # context: AdminRequestContext = request["context"] - cred_def_id = request.query.get("credentialDefinitionId") - issuer_id = request.query.get("issuerId") - schema_id = request.query.get("schemaId") - schema_issuer_id = request.query.get("schemaIssuerId") - schema_name = request.query.get("schemaName") - schema_version = request.query.get("schemaVersion") + cred_def_id = request.query.get("credential_definition_id") + issuer_id = request.query.get("issuer_id") + schema_id = request.query.get("schema_id") + schema_issuer_id = request.query.get("schema_issuer_id") + schema_name = request.query.get("schema_name") + schema_version = request.query.get("schema_version") return web.json_response( { diff --git a/aries_cloudagent/anoncreds/models/anoncreds_cred_def.py b/aries_cloudagent/anoncreds/models/anoncreds_cred_def.py index 9531d5ba3a..7fbd3d826e 100644 --- a/aries_cloudagent/anoncreds/models/anoncreds_cred_def.py +++ b/aries_cloudagent/anoncreds/models/anoncreds_cred_def.py @@ -1,64 +1,48 @@ """Anoncreds cred def OpenAPI validators""" -from marshmallow import Schema, fields -from aries_cloudagent.anoncreds.models.anoncreds_valid import ANONCREDS_SCHEMA_ID,ANONCREDS_VERSION +from typing import Any, Dict, List, Literal, Optional -from aries_cloudagent.messaging.valid import GENERIC_DID, INDY_CRED_DEF_ID, NUM_STR_WHOLE +from marshmallow import EXCLUDE, Schema, fields -from ...messaging.models.openapi import OpenAPISchema - - -# TODO: determine types for `primary` and `revocation` -class AnonCredsCredentialDefinitionValue: - """AnonCredsCredentialDefinitionValue""" - - primary: Any - revocation: Optional[Any] - - - -class AnonCredsCredentialDefinition: - """AnonCredsCredentialDefinition""" +from aries_cloudagent.anoncreds.models.anoncreds_valid import ( + ANONCREDS_SCHEMA_ID, + ANONCREDS_VERSION, +) +from aries_cloudagent.messaging.models.base import BaseModel, BaseModelSchema +from aries_cloudagent.messaging.valid import ( + GENERIC_DID, + INDY_CRED_DEF_ID, + NUM_STR_WHOLE, +) - issuerId: str - schemaId: str - type: Literal["CL"] - tag: str - value: AnonCredsCredentialDefinitionValue +from ...messaging.models.openapi import OpenAPISchema +from .anoncreds_schema import AnonCredsSchema -class AnonCredsRegistryGetCredentialDefinition: - """AnonCredsRegistryGetCredentialDefinition""" +class PrimarySchema(BaseModel): + """PrimarySchema""" - credential_definition: AnonCredsCredentialDefinition - credential_definition_id: str - resolution_metadata: Dict[str, Any] - credential_definition_metadata: Dict[str, Any] + class Meta: + """PrimarySchema metadata.""" + schema_class = "PrimarySchemaSchema" -class AnonCredsRevocationRegistryDefinition: - """AnonCredsRevocationRegistryDefinition""" + def __init__(self, n: str, s: str, r: dict, rctxt: str, z: str, **kwargs): + super().__init__(**kwargs) + self.n = n + self.s = s + self.r = r + self.rctxt = rctxt + self.z = z - issuerId: str - type: Literal["CL_ACCUM"] - credDefId: str - tag: str - # TODO: determine type for `publicKeys` - publicKeys: Any - maxCredNum: int - tailsLocation: str - tailsHash: str +class PrimarySchemaSchema(BaseModelSchema): + """Parameters and validators for credential definition primary.""" -class AnonCredsRegistryGetRevocationRegistryDefinition: - """AnonCredsRegistryGetRevocationRegistryDefinition""" - - revocation_registry: AnonCredsRevocationRegistryDefinition - revocation_registry_id: str - resolution_metadata: Dict[str, Any] - revocation_registry_metadata: Dict[str, Any] + class Meta: + """PrimarySchema metadata.""" -class PrimarySchema(OpenAPISchema): - """Parameters and validators for credential definition primary.""" + model_class = AnonCredsSchema + unknown = EXCLUDE n = fields.Str(**NUM_STR_WHOLE) s = fields.Str(**NUM_STR_WHOLE) @@ -76,123 +60,180 @@ class PrimarySchema(OpenAPISchema): z = fields.Str(**NUM_STR_WHOLE) -class CredDefValueSchema(OpenAPISchema): - """Parameters and validators for credential definition value.""" +# TODO: determine types for `primary` and `revocation` +class AnonCredsCredentialDefinitionValue(BaseModel): + """AnonCredsCredentialDefinitionValue""" - primary = fields.Nested(PrimarySchema()) + class Meta: + """AnonCredsCredentialDefinitionValue metadata.""" + schema_class = "AnonCredsCredentialDefinitionValueSchema" + def __init__(self, primary: PrimarySchema, **kwargs): + super().__init__(**kwargs) + self.primary = primary -class CredDefSchema(OpenAPISchema): - """Parameters and validators for credential definition.""" + # revocation: Optional[Any] - tag = fields.Str( - description="""The tag value passed in by the Issuer to - an AnonCred's Credential Definition create and store implementation.""" - ) - schemaId = fields.Str( - data_key="schemaId", description="Schema identifier", **ANONCREDS_SCHEMA_ID - ) - issuerId = fields.Str( - description="Issuer Identifier of the credential definition or schema", - **GENERIC_DID, - ) # TODO: get correct validator - supportRevocation = fields.Bool() - revocationRegistrySize = fields.Int() +class AnonCredsCredentialDefinitionValueSchema(BaseModelSchema): + """Parameters and validators for credential definition value.""" -class CredDefPostOptionsSchema(OpenAPISchema): - """Parameters and validators for credential definition options.""" + primary = fields.Nested(PrimarySchemaSchema()) - endorserConnectionId = fields.Str() - supportRevocation = fields.Bool() - revocationRegistrySize = fields.Int() +class AnonCredsCredentialDefinition(BaseModel): + """AnonCredsCredentialDefinition""" -class CredDefPostQueryStringSchema(OpenAPISchema): - """Parameters and validators for query string in create credential definition.""" + class Meta: + """AnonCredsCredentialDefinition metadata.""" - credentialDefinition = fields.Nested(CredDefSchema()) - options = fields.Nested(CredDefPostOptionsSchema()) + schema_class = "AnonCredsCredentialDefinitionSchema" + def __init__( + self, + issuer_id: str, + schema_id: str, + type: Literal["CL"], + tag: str, + value: AnonCredsCredentialDefinitionValue, + **kwargs + ): + self.issuer_id = issuer_id + self.schema_id = schema_id + self.type = type + self.tag = tag + self.value = value -class CredDefsQueryStringSchema(OpenAPISchema): - """Parameters and validators for credential definition list query.""" - credentialDefinitionId = fields.Str( - description="Credential definition identifier", - **INDY_CRED_DEF_ID, - ) - issuerId = fields.Str( - description="Issuer Identifier of the credential definition or schema", - **GENERIC_DID, - ) # TODO: get correct validator - schemaId = fields.Str( - data_key="schemaId", description="Schema identifier", **ANONCREDS_SCHEMA_ID - ) - schemaIssuerId = fields.Str( - description="Issuer Identifier of the credential definition or schema", - **GENERIC_DID, - ) # TODO: get correct validator - schemaName = fields.Str( - description="Schema name", - example=ANONCREDS_SCHEMA_ID["example"].split(":")[2], - ) - schemaVersion = fields.Str(description="Schema version", **ANONCREDS_VERSION) +class AnonCredsCredentialDefinitionSchema(BaseModelSchema): + """AnonCredsCredentialDefinitionSchema""" + class Meta: + """AnonCredsCredentialDefinitionSchema metadata.""" -class CredDefResponseSchema(OpenAPISchema): - """Parameters and validators for credential definition response.""" + model_class = AnonCredsCredentialDefinition + unknown = EXCLUDE - issuerId = fields.Str( + issuer_id = fields.Str( description="Issuer Identifier of the credential definition or schema", **GENERIC_DID, + data_key="issuerId", ) # TODO: get correct validator - schemaId = fields.Str( + schema_id = fields.Str( data_key="schemaId", description="Schema identifier", **ANONCREDS_SCHEMA_ID ) + type = fields.Str() tag = fields.Str( - description="The tag value passed in by the Issuer to an\ - AnonCred's Credential Definition create and store implementation." + description="""The tag value passed in by the Issuer to + an AnonCred's Credential Definition create and store implementation.""" ) - value = fields.Nested(CredDefValueSchema()) - # registration_metadata = fields.Bool() - # revocationRegistrySize = fields.Int() + value = fields.Nested(AnonCredsCredentialDefinitionValueSchema()) -class CredDefState(OpenAPISchema): - """Parameters and validators for credential definition state.""" - - state = fields.Str() # TODO: create validator for only possible states - credential_definition_id = fields.Str( - description="Credential definition identifier", - **INDY_CRED_DEF_ID, - ) - credential_definition = fields.Nested(CredDefResponseSchema()) +class AnonCredsRegistryGetCredentialDefinition(BaseModel): + """AnonCredsRegistryGetCredentialDefinition""" + class Meta: + """AnonCredsRegistryGetCredentialDefinition metadata.""" -class PostCredDefResponseSchema(OpenAPISchema): - """Parameters and validators for credential definition create response.""" + schema_class = "AnonCredsRegistryGetCredentialDefinitionSchema" - job_id = fields.Str() - credential_definition_state = fields.Nested(CredDefState()) - registration_metadata = fields.Dict() - credential_definition_metadata = fields.Dict() + def __init__( + self, + credential_definition: AnonCredsCredentialDefinition, + credential_definition_id: str, + resolution_metadata: dict, + credential_definition_metadata: dict, + **kwargs + ): + super().__init__(**kwargs) + self.credential_definition = credential_definition + self.credential_definition_id = credential_definition_id + self.resolution_metadata = resolution_metadata + self.credential_definition_metadata = credential_definition_metadata -class GetCredDefResponseSchema(OpenAPISchema): +class AnonCredsRegistryGetCredentialDefinitionSchema(BaseModelSchema): """Parameters and validators for credential definition list response.""" + class Meta: + """AnonCredsRegistryGetCredentialDefinitionSchema metadata.""" + + model_class = AnonCredsRegistryGetCredentialDefinition + unknown = EXCLUDE + credential_definition_id = fields.Str( description="Credential definition identifier", **INDY_CRED_DEF_ID, ) - credential_definition = fields.Nested(CredDefResponseSchema()) + credential_definition = fields.Nested(AnonCredsCredentialDefinitionSchema()) resolution_metadata = fields.Dict() credential_definition_metadata = fields.Dict() -class GetCredDefsResponseSchema(OpenAPISchema): - """Parameters and validators for credential definition list all response.""" +class AnonCredsRevocationRegistryDefinition(BaseModel): + """AnonCredsRevocationRegistryDefinition""" + + class Meta: + """AnonCredsRevocationRegistryDefinition metadata.""" + + schema_class = "AnonCredsRevocationRegistryDefinitionSchema" + + def __init__( + self, + issuer_id: str, + type: Literal["CL_ACCUM"], + cred_def_id: str, + tag: str, + # TODO: determine type for `publicKeys` + public_keys: Any, + max_cred_num: int, + tails_Location: str, + tails_hash: str, + **kwargs + ): + super().__init__(**kwargs) + self.issuer_id = issuer_id + self.type = type + self.cred_def_id = cred_def_id + self.tag = tag + self.public_keys = public_keys + self.max_cred_num = max_cred_num + self.tails_location = tails_Location + self.tails_hash = tails_hash + + +class AnonCredsRevocationRegistryDefinitionSchema(BaseModelSchema): + """AnonCredsRevocationRegistryDefinitionSchema""" + + class Meta: + """AnonCredsRevocationRegistryDefinitionSchema metadata.""" + + model_class = AnonCredsRevocationRegistryDefinition + unknown = EXCLUDE + + +class AnonCredsRegistryGetRevocationRegistryDefinition(BaseModel): + """AnonCredsRegistryGetRevocationRegistryDefinition""" + + class Meta: + """AnonCredsRegistryGetRevocationRegistryDefinition metadata.""" + + schema_class = "AnonCredsRegistryGetRevocationRegistryDefinitionSchema" + + def __init__(self, **kwargs): + super().__init__(**kwargs) + + revocation_registry: AnonCredsRevocationRegistryDefinition + revocation_registry_id: str + resolution_metadata: Dict[str, Any] + revocation_registry_metadata: Dict[str, Any] + + +class AnonCredsRegistryGetRevocationRegistryDefinitionSchema(BaseModelSchema): + class Meta: + """AnonCredsRegistryGetRevocationRegistryDefinitionSchema metadata.""" - credential_definition_id = fields.Str() \ No newline at end of file + model_class = AnonCredsRegistryGetRevocationRegistryDefinition + unknown = EXCLUDE diff --git a/aries_cloudagent/anoncreds/models/anoncreds_schema.py b/aries_cloudagent/anoncreds/models/anoncreds_schema.py index 86c3f86fc9..1315c61296 100644 --- a/aries_cloudagent/anoncreds/models/anoncreds_schema.py +++ b/aries_cloudagent/anoncreds/models/anoncreds_schema.py @@ -3,11 +3,10 @@ from typing import Any, Dict, List from marshmallow import EXCLUDE, fields -from pydantic import BaseModel from aries_cloudagent.anoncreds.models.anoncreds_valid import ( ANONCREDS_SCHEMA_ID, ANONCREDS_VERSION) -from aries_cloudagent.messaging.models.base import BaseModelSchema +from aries_cloudagent.messaging.models.base import BaseModel, BaseModelSchema from ...messaging.models.openapi import OpenAPISchema from ...messaging.valid import GENERIC_DID, UUIDFour @@ -104,7 +103,7 @@ class SchemaState(OpenAPISchema): state = fields.Str() # TODO: create validator for only possible states schema_id = fields.Str( - data_key="schemaId", description="Schema identifier", **INDY_SCHEMA_ID + data_key="schemaId", description="Schema identifier", **ANONCREDS_SCHEMA_ID ) schema_ = fields.Nested(AnonCredsSchemaSchema(), data_key="schema") @@ -114,7 +113,7 @@ class SchemasResponseSchema(OpenAPISchema): schema_id = fields.List( fields.Str( - data_key="schemaId", description="Schema identifier", **INDY_SCHEMA_ID + data_key="schemaId", description="Schema identifier", **ANONCREDS_SCHEMA_ID ) ) @@ -134,9 +133,9 @@ class SchemasQueryStringSchema(OpenAPISchema): schemaName = fields.Str( description="Schema name", - example=INDY_SCHEMA_ID["example"].split(":")[2], + example=ANONCREDS_SCHEMA_ID["example"].split(":")[2], ) - schemaVersion = fields.Str(description="Schema version", **INDY_VERSION) + schemaVersion = fields.Str(description="Schema version", **ANONCREDS_VERSION) schemaIssuerDid = fields.Str( description="Issuer Identifier of the credential definition or schema", **GENERIC_DID, From 22dc4ce5817a9d2db4cede9b9f5b8943ef7f36e8 Mon Sep 17 00:00:00 2001 From: Adam Burdett Date: Thu, 16 Mar 2023 11:58:54 -0600 Subject: [PATCH 042/150] models work Signed-off-by: Adam Burdett --- .../anoncreds/anoncreds/anoncreds_registry.py | 2 +- .../anoncreds/anoncreds/base_registry.py | 2 +- .../default/did_indy_registry/registry.py | 2 +- .../default/did_web_registry/registry.py | 2 +- .../default/legacy_indy_registry/registry.py | 2 +- .../anoncreds/anoncreds/models.py | 25 ---- .../anoncreds/anoncreds/routes.py | 13 +- aries_cloudagent/anoncreds/issuer.py | 4 +- .../anoncreds/models/anoncreds_cred_def.py | 133 +++++++++++++++++- .../anoncreds/models/anoncreds_schema.py | 4 +- .../anoncreds/models/anoncreds_valid.py | 13 +- aries_cloudagent/anoncreds/sdk/holder.py | 12 +- 12 files changed, 160 insertions(+), 54 deletions(-) delete mode 100644 aries_cloudagent/anoncreds/anoncreds/models.py diff --git a/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py b/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py index a29d89229a..de3eedc0dd 100644 --- a/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py @@ -3,7 +3,7 @@ from typing import List from .base_registry import BaseRegistry -from .models import ( +from ..models.anoncreds_cred_def import ( AnonCredsRegistryGetCredentialDefinition, AnonCredsRegistryGetRevocationList, AnonCredsRegistryGetRevocationRegistryDefinition, diff --git a/aries_cloudagent/anoncreds/anoncreds/base_registry.py b/aries_cloudagent/anoncreds/anoncreds/base_registry.py index 6df0ff3a80..bb606ef6a0 100644 --- a/aries_cloudagent/anoncreds/anoncreds/base_registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/base_registry.py @@ -3,7 +3,7 @@ from typing import List from ...config.injection_context import InjectionContext -from .models import ( +from ..models.anoncreds_cred_def import ( AnonCredsRegistryGetCredentialDefinition, AnonCredsRegistryGetRevocationList, AnonCredsRegistryGetRevocationRegistryDefinition, diff --git a/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py index a9ffec826f..0155bc3468 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py @@ -1,5 +1,5 @@ """DID Indy Registry""" -from ...models import ( +from ....models.anoncreds_cred_def import ( AnonCredsRegistryGetCredentialDefinition, AnonCredsRegistryGetRevocationList, AnonCredsRegistryGetRevocationRegistryDefinition, diff --git a/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py index 6e71bb92f0..4503c57400 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py @@ -1,5 +1,5 @@ """DID Web Registry""" -from ...models import ( +from ....models.anoncreds_cred_def import ( AnonCredsRegistryGetCredentialDefinition, AnonCredsRegistryGetRevocationList, AnonCredsRegistryGetRevocationRegistryDefinition, diff --git a/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py index 3b44b048a1..cac3aa9f02 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py @@ -1,5 +1,5 @@ """Legacy Indy Registry""" -from ...models import ( +from ....models.anoncreds_cred_def import ( AnonCredsRegistryGetCredentialDefinition, AnonCredsRegistryGetRevocationList, AnonCredsRegistryGetRevocationRegistryDefinition, diff --git a/aries_cloudagent/anoncreds/anoncreds/models.py b/aries_cloudagent/anoncreds/anoncreds/models.py deleted file mode 100644 index 1c87c730ce..0000000000 --- a/aries_cloudagent/anoncreds/anoncreds/models.py +++ /dev/null @@ -1,25 +0,0 @@ -"""AnonCreds Objects""" -from typing import Any, Dict, List, Optional -from typing_extensions import Literal -from dataclasses import dataclass - - - -@dataclass -class AnonCredsRevocationList: - """AnonCredsRevocationList""" - - issuerId: str - revRegId: str - revocationList: List[int] - currentAccumulator: str - timestamp: int - - -@dataclass -class AnonCredsRegistryGetRevocationList: - """AnonCredsRegistryGetRevocationList""" - - revocation_list: AnonCredsRevocationList - resolution_metadata: Dict[str, Any] - revocation_registry_metadata: Dict[str, Any] diff --git a/aries_cloudagent/anoncreds/anoncreds/routes.py b/aries_cloudagent/anoncreds/anoncreds/routes.py index 002e805473..70835742d1 100644 --- a/aries_cloudagent/anoncreds/anoncreds/routes.py +++ b/aries_cloudagent/anoncreds/anoncreds/routes.py @@ -12,12 +12,9 @@ from marshmallow import fields from aries_cloudagent.anoncreds.anoncreds.anoncreds_registry import AnonCredsRegistry from aries_cloudagent.anoncreds.models.anoncreds_cred_def import ( - CredDefPostQueryStringSchema, AnonCredsCredentialDefinitionValueSchema, - CredDefsQueryStringSchema, AnonCredsRegistryGetCredentialDefinitionSchema, - GetCredDefsResponseSchema, - PostCredDefResponseSchema, + AnonCredsCredentialDefinitionSchema, ) from aries_cloudagent.anoncreds.models.anoncreds_schema import ( @@ -27,6 +24,10 @@ SchemasQueryStringSchema, SchemasResponseSchema, ) +from aries_cloudagent.anoncreds.models.anoncreds_valid import ( + ANONCREDS_SCHEMA_ID, + ANONCREDS_VERSION, +) from ...admin.request_context import AdminRequestContext from ...messaging.models.openapi import OpenAPISchema @@ -143,14 +144,12 @@ class PostCredDefResponseSchema(OpenAPISchema): credential_definition_metadata = fields.Dict() - - - class GetCredDefsResponseSchema(OpenAPISchema): """Parameters and validators for credential definition list all response.""" credential_definition_id = fields.Str() + @docs(tags=["anoncreds"], summary="") @request_schema(SchemaPostQueryStringSchema()) @response_schema(PostSchemaResponseSchema(), 200, description="") diff --git a/aries_cloudagent/anoncreds/issuer.py b/aries_cloudagent/anoncreds/issuer.py index e4fbd7f687..49704626ca 100644 --- a/aries_cloudagent/anoncreds/issuer.py +++ b/aries_cloudagent/anoncreds/issuer.py @@ -32,9 +32,7 @@ def __repr__(self) -> str: return "<{}>".format(self.__class__.__name__) @staticmethod - def make_schema_id( - origin_did: str, schema_name: str, schema_version: str - ) -> str: + def make_schema_id(origin_did: str, schema_name: str, schema_version: str) -> str: """Derive the ID for a schema.""" return f"{origin_did}:2:{schema_name}:{schema_version}" diff --git a/aries_cloudagent/anoncreds/models/anoncreds_cred_def.py b/aries_cloudagent/anoncreds/models/anoncreds_cred_def.py index 7fbd3d826e..3fff6b2526 100644 --- a/aries_cloudagent/anoncreds/models/anoncreds_cred_def.py +++ b/aries_cloudagent/anoncreds/models/anoncreds_cred_def.py @@ -79,6 +79,12 @@ def __init__(self, primary: PrimarySchema, **kwargs): class AnonCredsCredentialDefinitionValueSchema(BaseModelSchema): """Parameters and validators for credential definition value.""" + class Meta: + """AnonCredsCredentialDefinitionValueSchema metadata.""" + + model_class = AnonCredsCredentialDefinitionValue + unknown = EXCLUDE + primary = fields.Nested(PrimarySchemaSchema()) @@ -213,6 +219,24 @@ class Meta: model_class = AnonCredsRevocationRegistryDefinition unknown = EXCLUDE + issuer_id = fields.Str( + description="Issuer Identifier of the credential definition or schema", + **GENERIC_DID, + data_key="issuerId", + ) # TODO: get correct validator + type = fields.Str() + cred_def_id = fields.Str( + description="Credential definition identifier", + **INDY_CRED_DEF_ID, + data_key="credDefId", + ) + tag = fields.Str(description="""""") + # TODO: type for public key + public_keys = fields.Str(data_key="publicKeys") + max_cred_num = fields.Int(data_key="maxCredNum") + tails_location = fields.Str(data_key="tailsLocation") + tails_hash = fields.Str(data_key="tailsHash") + class AnonCredsRegistryGetRevocationRegistryDefinition(BaseModel): """AnonCredsRegistryGetRevocationRegistryDefinition""" @@ -222,13 +246,19 @@ class Meta: schema_class = "AnonCredsRegistryGetRevocationRegistryDefinitionSchema" - def __init__(self, **kwargs): + def __init__( + self, + revocation_registry: AnonCredsRevocationRegistryDefinition, + revocation_registry_id: str, + resolution_metadata: Dict[str, Any], + revocation_registry_metadata: Dict[str, Any], + **kwargs + ): super().__init__(**kwargs) - - revocation_registry: AnonCredsRevocationRegistryDefinition - revocation_registry_id: str - resolution_metadata: Dict[str, Any] - revocation_registry_metadata: Dict[str, Any] + self.revocation_registry = revocation_registry + self.revocation_registry_id = revocation_registry_id + self.resolution_metadata = resolution_metadata + self.revocation_registry_metadata = revocation_registry_metadata class AnonCredsRegistryGetRevocationRegistryDefinitionSchema(BaseModelSchema): @@ -237,3 +267,94 @@ class Meta: model_class = AnonCredsRegistryGetRevocationRegistryDefinition unknown = EXCLUDE + + revocation_registry = fields.Nested(AnonCredsRevocationRegistryDefinitionSchema()) + revocation_registry_id = fields.Str() + resolution_metadata = fields.Dict() + revocation_registry_metadata = fields.Dict() + + +class AnonCredsRevocationList(BaseModel): + """AnonCredsRevocationList""" + + class Meta: + """AnonCredsRevocationList metadata.""" + + schema_class = "AnonCredsRevocationListSchema" + + def __init__( + self, + issuer_id: str, + rev_reg_id: str, + revocation_list: List[int], + current_accumulator: str, + timestamp: int, + **kwargs + ): + super().__init__(**kwargs) + self.issuer_id = issuer_id + self.rev_reg_id = rev_reg_id + self.revocation_list = revocation_list + self.current_accumulator = current_accumulator + self.timestamp = timestamp + + +class AnonCredsRevocationListSchema(BaseModelSchema): + """AnonCredsRevocationListSchema""" + + class Meta: + """AnonCredsRevocationListSchema metadata.""" + + model_class = AnonCredsRevocationList + unknown = EXCLUDE + + issuer_id = fields.Str( + description="Issuer Identifier of the credential definition or schema", + **GENERIC_DID, + data_key="issuerId", + ) # TODO: get correct validator + rev_reg_id = fields.Str( + description="", + **GENERIC_DID, + data_key="revRegId", + ) # TODO: get correct validator + revocation_list = fields.List( + fields.Str(description=""), description="", data_key="revocationList" + ) + current_accumulator = fields.Str(data_key="currentAccumulator") + timestamp = fields.Int() + + +class AnonCredsRegistryGetRevocationList(BaseModel): + """AnonCredsRegistryGetRevocationList""" + + class Meta: + """AnonCredsRegistryGetRevocationList metadata.""" + + schema_class = "AnonCredsRegistryGetRevocationListSchema" + + def __init__( + self, + revocation_list: AnonCredsRevocationList, + resolution_metadata: Dict[str, Any], + revocation_registry_metadata: Dict[str, Any], + **kwargs + ): + super().__init__(**kwargs) + self.revocation_list = revocation_list + self.resolution_metadata = resolution_metadata + self.revocation_registry_metadata = revocation_registry_metadata + + +class AnonCredsRegistryGetRevocationListSchema(BaseModelSchema): + """AnonCredsRegistryGetRevocationListSchema""" + + class Meta: + """AnonCredsRegistryGetRevocationListSchema metadata.""" + + model_class = AnonCredsRegistryGetRevocationList + unknown = EXCLUDE + + revocation_list = fields.Nested(AnonCredsRevocationListSchema) + resolution_metadata = fields.Str() + revocation_registry_metadata = fields.Dict() diff --git a/aries_cloudagent/anoncreds/models/anoncreds_schema.py b/aries_cloudagent/anoncreds/models/anoncreds_schema.py index 1315c61296..ef278076b8 100644 --- a/aries_cloudagent/anoncreds/models/anoncreds_schema.py +++ b/aries_cloudagent/anoncreds/models/anoncreds_schema.py @@ -5,7 +5,9 @@ from marshmallow import EXCLUDE, fields from aries_cloudagent.anoncreds.models.anoncreds_valid import ( - ANONCREDS_SCHEMA_ID, ANONCREDS_VERSION) + ANONCREDS_SCHEMA_ID, + ANONCREDS_VERSION, +) from aries_cloudagent.messaging.models.base import BaseModel, BaseModelSchema from ...messaging.models.openapi import OpenAPISchema diff --git a/aries_cloudagent/anoncreds/models/anoncreds_valid.py b/aries_cloudagent/anoncreds/models/anoncreds_valid.py index 681af03434..11ee6e6b83 100644 --- a/aries_cloudagent/anoncreds/models/anoncreds_valid.py +++ b/aries_cloudagent/anoncreds/models/anoncreds_valid.py @@ -3,6 +3,7 @@ B58 = alphabet if isinstance(alphabet, str) else alphabet.decode("ascii") + class AnonCredsSchemaId(Regexp): """Validate value against indy schema identifier specification.""" @@ -17,6 +18,7 @@ def __init__(self): error="Value {input} is not an indy schema identifier", ) + class AnonCredsVersion(Regexp): """Validate value against indy version specification.""" @@ -31,5 +33,12 @@ def __init__(self): error="Value {input} is not an indy version (use only digits and '.')", ) -ANONCREDS_SCHEMA_ID = {"validate": AnonCredsSchemaId(), "example": AnonCredsSchemaId.EXAMPLE} -ANONCREDS_VERSION = {"validate": AnonCredsVersion(), "example": AnonCredsVersion.EXAMPLE} + +ANONCREDS_SCHEMA_ID = { + "validate": AnonCredsSchemaId(), + "example": AnonCredsSchemaId.EXAMPLE, +} +ANONCREDS_VERSION = { + "validate": AnonCredsVersion(), + "example": AnonCredsVersion.EXAMPLE, +} diff --git a/aries_cloudagent/anoncreds/sdk/holder.py b/aries_cloudagent/anoncreds/sdk/holder.py index 7ad78b63a0..fb3e9e8825 100644 --- a/aries_cloudagent/anoncreds/sdk/holder.py +++ b/aries_cloudagent/anoncreds/sdk/holder.py @@ -226,11 +226,13 @@ async def fetch(reft, limit): with IndyErrorHandler( "Error when constructing wallet credential query", AnonCredsHolderError ): - search_handle = await ( - indy.anoncreds.prover_search_credentials_for_proof_req( - self.wallet.handle, - json.dumps(presentation_request), - json.dumps(extra_query), + search_handle = ( + await ( + indy.anoncreds.prover_search_credentials_for_proof_req( + self.wallet.handle, + json.dumps(presentation_request), + json.dumps(extra_query), + ) ) ) From 2f5bc91325c50f203ca9270886fcc225f993f029 Mon Sep 17 00:00:00 2001 From: Adam Burdett Date: Thu, 16 Mar 2023 13:47:47 -0600 Subject: [PATCH 043/150] fixed import Signed-off-by: Adam Burdett --- aries_cloudagent/anoncreds/models/anoncreds_cred_def.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/aries_cloudagent/anoncreds/models/anoncreds_cred_def.py b/aries_cloudagent/anoncreds/models/anoncreds_cred_def.py index 3fff6b2526..97a2b972b1 100644 --- a/aries_cloudagent/anoncreds/models/anoncreds_cred_def.py +++ b/aries_cloudagent/anoncreds/models/anoncreds_cred_def.py @@ -1,5 +1,6 @@ """Anoncreds cred def OpenAPI validators""" -from typing import Any, Dict, List, Literal, Optional +from typing import Any, Dict, List, Optional +from typing_extensions import Literal from marshmallow import EXCLUDE, Schema, fields From 18d2231c8862189af894dede983a330d19033782 Mon Sep 17 00:00:00 2001 From: Adam Burdett Date: Thu, 16 Mar 2023 14:14:55 -0600 Subject: [PATCH 044/150] start up bug Signed-off-by: Adam Burdett --- aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py | 7 ++++++- aries_cloudagent/anoncreds/anoncreds/base_registry.py | 6 +++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py b/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py index de058f97d0..b9cf1e8529 100644 --- a/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py @@ -26,7 +26,12 @@ def __init__(self, registries: Optional[List[BaseAnonCredsHandler]] = None): if registries: for registry in registries: self.register(registry) - + + @property + def supported_identifiers_regex(self): + """Return supported identifiers.""" + return "" #TODO: implement me + def register(self, registry: BaseAnonCredsHandler): """Register a new registry.""" if isinstance(registry, BaseAnonCredsResolver): diff --git a/aries_cloudagent/anoncreds/anoncreds/base_registry.py b/aries_cloudagent/anoncreds/anoncreds/base_registry.py index 0afeba54a5..77715fba62 100644 --- a/aries_cloudagent/anoncreds/anoncreds/base_registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/base_registry.py @@ -1,5 +1,5 @@ """Base Registry""" -from abc import ABC, abstractmethod, abstractproperty +from abc import ABC, abstractmethod from typing import Pattern from ...config.injection_context import InjectionContext @@ -24,10 +24,10 @@ class AnonCredsRegistrationFailed(BaseAnonCredsError): class BaseAnonCredsHandler(ABC): - @abstractproperty + @property + @abstractmethod def supported_identifiers_regex(self) -> Pattern: """Regex to match supported identifiers.""" - ... async def supports(self, identifier: str) -> bool: """Determine whether this registry supports the given identifier.""" From 162ca2d1faf7b58b31d56812e671ac4e3de65d82 Mon Sep 17 00:00:00 2001 From: Adam Burdett Date: Thu, 16 Mar 2023 14:26:10 -0600 Subject: [PATCH 045/150] running code Signed-off-by: Adam Burdett --- .../anoncreds/anoncreds/__init__.py | 18 +++++++++--------- .../default/did_indy_registry/registry.py | 4 ++++ .../default/did_web_registry/registry.py | 4 ++++ .../default/legacy_indy_registry/registry.py | 4 ++++ 4 files changed, 21 insertions(+), 9 deletions(-) diff --git a/aries_cloudagent/anoncreds/anoncreds/__init__.py b/aries_cloudagent/anoncreds/anoncreds/__init__.py index 9506346d5d..6429951a70 100644 --- a/aries_cloudagent/anoncreds/anoncreds/__init__.py +++ b/aries_cloudagent/anoncreds/anoncreds/__init__.py @@ -18,28 +18,28 @@ async def setup(context: InjectionContext): indy_registry = ClassProvider( "aries_cloudagent.anoncreds.anoncreds.default.did_indy_registry.registry" ".DIDIndyRegistry", - supported_identifiers=[], - method_name="did:indy", + #supported_identifiers=[], + #method_name="did:indy", ).provide(context.settings, context.injector) await indy_registry.setup(context) - registry.register_registry(indy_registry) + registry.register(indy_registry) web_registry = ClassProvider( "aries_cloudagent.anoncreds.anoncreds.default.did_web_registry.registry" ".DIDWebRegistry", - supported_identifiers=[], - method_name="did:web", + #supported_identifiers=[], + #method_name="did:web", ).provide(context.settings, context.injector) await web_registry.setup(context) - registry.register_registry(web_registry) + registry.register(web_registry) legacy_indy_registry = ClassProvider( "aries_cloudagent.anoncreds.anoncreds.default.legacy_indy_registry.registry" ".LegacyIndyRegistry", - supported_identifiers=[], - method_name="", + #supported_identifiers=[], + #method_name="", ).provide(context.settings, context.injector) await legacy_indy_registry.setup(context) - registry.register_registry(legacy_indy_registry) + registry.register(legacy_indy_registry) # TODO: add context.settings diff --git a/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py index 1594186d88..2df3036bdd 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py @@ -12,6 +12,10 @@ class DIDIndyRegistry(BaseAnonCredsResolver, BaseAnonCredsRegistrar): """DIDIndyRegistry""" + @property + def supported_identifiers_regex(self): + return "" # TODO: implement me + async def setup(self, context: InjectionContext): """Setup.""" print("Successfully registered DIDIndyRegistry") diff --git a/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py index f91c83e6bd..ded3aad98d 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py @@ -12,6 +12,10 @@ class DIDWebRegistry(BaseAnonCredsResolver, BaseAnonCredsRegistrar): """DIDWebRegistry""" + @property + def supported_identifiers_regex(self): + return "" # TODO: implement me + async def setup(self, context: InjectionContext): """Setup.""" print("Successfully registered DIDWebRegistry") diff --git a/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py index e5a8f75727..a6fad3dc2f 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py @@ -12,6 +12,10 @@ class LegacyIndyRegistry(BaseAnonCredsResolver, BaseAnonCredsRegistrar): """LegacyIndyRegistry""" + @property + def supported_identifiers_regex(self): + return "" # TODO: implement me + async def setup(self, context: InjectionContext): """Setup.""" print("Successfully registered LegacyIndyRegistry") From a7d9d05edc51ef4bf482fd94e119ef596a6eab30 Mon Sep 17 00:00:00 2001 From: Adam Burdett Date: Thu, 16 Mar 2023 15:57:36 -0600 Subject: [PATCH 046/150] post schema Signed-off-by: Adam Burdett --- .../anoncreds/anoncreds/routes.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/aries_cloudagent/anoncreds/anoncreds/routes.py b/aries_cloudagent/anoncreds/anoncreds/routes.py index 70835742d1..e7122b0898 100644 --- a/aries_cloudagent/anoncreds/anoncreds/routes.py +++ b/aries_cloudagent/anoncreds/anoncreds/routes.py @@ -18,6 +18,7 @@ ) from aries_cloudagent.anoncreds.models.anoncreds_schema import ( + AnonCredsSchema, PostSchemaResponseSchema, SchemaPostQueryStringSchema, AnonCredsRegistryGetSchemaSchema, @@ -188,10 +189,20 @@ async def schemas_post(request: web.BaseRequest): schema_metadata : This fields contains metadata about the schema. """ - # context: AdminRequestContext = request["context"] - parameters = await request.json() - - return web.json_response({"input": parameters}) + context: AdminRequestContext = request["context"] + anon_creds_registry = context.inject(AnonCredsRegistry) + request_data = await request.json() + options = request_data.get("option") + schema_data = request_data.get("schema") + + schema: AnonCredsSchema = AnonCredsSchema( + issuer_id=schema_data.get("issuerId"), + attr_names=schema_data.get("attrNames"), + name=schema_data.get("name"), + version=schema_data.get("version"), + ) + result = anon_creds_registry.register_schema(options,schema) + return web.json_response(result) @docs(tags=["anoncreds"], summary="") From 410a740c1dfd566f12baac8921b61631168e1551 Mon Sep 17 00:00:00 2001 From: Adam Burdett Date: Fri, 17 Mar 2023 12:05:05 -0600 Subject: [PATCH 047/150] routes logic Signed-off-by: Adam Burdett --- .../anoncreds/anoncreds/routes.py | 83 +++++++++++-------- 1 file changed, 47 insertions(+), 36 deletions(-) diff --git a/aries_cloudagent/anoncreds/anoncreds/routes.py b/aries_cloudagent/anoncreds/anoncreds/routes.py index e7122b0898..7b142e8757 100644 --- a/aries_cloudagent/anoncreds/anoncreds/routes.py +++ b/aries_cloudagent/anoncreds/anoncreds/routes.py @@ -64,10 +64,10 @@ class CredIdMatchInfo(OpenAPISchema): class CredDefSchema(OpenAPISchema): """Parameters and validators for credential definition.""" - tag = fields.Str( - description="""The tag value passed in by the Issuer to - an AnonCred's Credential Definition create and store implementation.""" - ) + # tag = fields.Str( + # description="""The tag value passed in by the Issuer to + # an AnonCred's Credential Definition create and store implementation.""" + # ) schemaId = fields.Str( data_key="schemaId", description="Schema identifier", **ANONCREDS_SCHEMA_ID ) @@ -201,7 +201,7 @@ async def schemas_post(request: web.BaseRequest): name=schema_data.get("name"), version=schema_data.get("version"), ) - result = anon_creds_registry.register_schema(options,schema) + result = anon_creds_registry.register_schema(options, schema) return web.json_response(result) @@ -237,18 +237,18 @@ async def schemas_get(request: web.BaseRequest): Returns: """ - # context: AdminRequestContext = request["context"] + context: AdminRequestContext = request["context"] + anon_creds_registry = context.inject(AnonCredsRegistry) schema_issuer_did = request.query.get("schemaIssuerDid") schema_name = request.query.get("schemaName") schema_version = request.query.get("schemaVersion") - - return web.json_response( - { - "schema_issuer_did": schema_issuer_did, - "schema_name": schema_name, - "schema_version": schema_version, - } - ) + filter = { + "issuerId": schema_issuer_did, + "name": schema_name, + "version": schema_version, + } + schema_ids = anon_creds_registry.get_schemas(filter) + return web.json_response(schema_ids) @docs(tags=["anoncreds"], summary="") @@ -262,9 +262,22 @@ async def cred_def_post(request: web.BaseRequest): Returns: """ - # context: AdminRequestContext = request["context"] + context: AdminRequestContext = request["context"] + anon_creds_registry = context.inject(AnonCredsRegistry) + request_data = await request.json() + options = request_data.get("option") + data = request_data.get("credentialDefinition") + + cred_def = { + "issuer_id": data.get("issuerId"), + "schema_id": data.get("schemaId"), + # "tag":data.get("tag"), + "support_revocation": data.get("supportRevocation"), + "revocation_registrySize": data.get("revocationRegistrySize"), + } + result = anon_creds_registry.register_credential_definition(options, cred_def) parameters = await request.json() - return web.json_response({"input": parameters}) + return web.json_response(result) @docs(tags=["anoncreds"], summary="") @@ -278,9 +291,11 @@ async def cred_def_get(request: web.BaseRequest): Returns: """ - # context: AdminRequestContext = request["context"] + context: AdminRequestContext = request["context"] + anon_creds_registry = context.inject(AnonCredsRegistry) credential_id = request.match_info["cred_def_id"] - return web.json_response({"cred_def_id": credential_id}) + result = await anon_creds_registry.get_credential_definition(credential_id) + return web.json_response(result) @docs(tags=["anoncreds"], summary="") @@ -294,24 +309,20 @@ async def cred_defs_get(request: web.BaseRequest): Returns: """ - # context: AdminRequestContext = request["context"] - cred_def_id = request.query.get("credential_definition_id") - issuer_id = request.query.get("issuer_id") - schema_id = request.query.get("schema_id") - schema_issuer_id = request.query.get("schema_issuer_id") - schema_name = request.query.get("schema_name") - schema_version = request.query.get("schema_version") - - return web.json_response( - { - "cred_def_id": cred_def_id, - "issuer_id": issuer_id, - "schema_id": schema_id, - "schema_issuer_id": schema_issuer_id, - "schema_name": schema_name, - "schema_version": schema_version, - } - ) + context: AdminRequestContext = request["context"] + anon_creds_registry = context.inject(AnonCredsRegistry) + query = request.query + + filter = { + "cred_def_id": query.get("credentialDefinitionId"), + "issuer_id": query.get("issuerId"), + "schema_id": query.get("schemaId"), + "schema_issuer_id": query.get("schemaIssuerId"), + "schema_name": query.get("schemaName"), + "schema_version": query.get("schemaVersion"), + } + cred_def_ids = anon_creds_registry.get_credential_definitions(filter) + return web.json_response(cred_def_ids) async def register(app: web.Application): From 75d41c209b0738474e006c4e0cbe1cd88ae45f7d Mon Sep 17 00:00:00 2001 From: Adam Burdett Date: Fri, 17 Mar 2023 13:56:58 -0600 Subject: [PATCH 048/150] deserialize Signed-off-by: Adam Burdett --- aries_cloudagent/anoncreds/anoncreds/routes.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/aries_cloudagent/anoncreds/anoncreds/routes.py b/aries_cloudagent/anoncreds/anoncreds/routes.py index 7b142e8757..41d4ef9564 100644 --- a/aries_cloudagent/anoncreds/anoncreds/routes.py +++ b/aries_cloudagent/anoncreds/anoncreds/routes.py @@ -195,12 +195,8 @@ async def schemas_post(request: web.BaseRequest): options = request_data.get("option") schema_data = request_data.get("schema") - schema: AnonCredsSchema = AnonCredsSchema( - issuer_id=schema_data.get("issuerId"), - attr_names=schema_data.get("attrNames"), - name=schema_data.get("name"), - version=schema_data.get("version"), - ) + schema: AnonCredsSchema = AnonCredsSchema.deserialize(schema_data) + #TODO: serialize return stuff. result = anon_creds_registry.register_schema(options, schema) return web.json_response(result) @@ -268,6 +264,7 @@ async def cred_def_post(request: web.BaseRequest): options = request_data.get("option") data = request_data.get("credentialDefinition") + # TODO: find out if we need a model for this input. cred_def = { "issuer_id": data.get("issuerId"), "schema_id": data.get("schemaId"), From 9a47e67d2167d6140a95a5be7fb0456268c8ee8d Mon Sep 17 00:00:00 2001 From: Char Howland Date: Thu, 16 Mar 2023 13:06:15 -0600 Subject: [PATCH 049/150] feat: registry method logic (WIP) Signed-off-by: Char Howland --- .../default/did_indy_registry/registry.py | 281 +++++++++++++++++- 1 file changed, 268 insertions(+), 13 deletions(-) diff --git a/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py index 2df3036bdd..c515f826d5 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py @@ -1,13 +1,36 @@ """DID Indy Registry""" -from ....models.anoncreds_cred_def import ( - AnonCredsRegistryGetCredentialDefinition, - AnonCredsRegistryGetRevocationList, - AnonCredsRegistryGetRevocationRegistryDefinition, -) -from ....models.anoncreds_schema import AnonCredsRegistryGetSchema +import logging +from asyncio import shield + from .....config.injection_context import InjectionContext -from ...base_registry import BaseAnonCredsResolver, BaseAnonCredsRegistrar +from .....connections.models.conn_record import ConnRecord +from .....core.profile import Profile +from .....ledger.base import BaseLedger +from .....ledger.error import LedgerError +from .....ledger.multiple_ledger.ledger_requests_executor import ( + GET_CRED_DEF, GET_SCHEMA, IndyLedgerRequestsExecutor) +from .....messaging.credential_definitions.util import ( + CRED_DEF_SENT_RECORD_TYPE, notify_cred_def_event) +from .....messaging.models.base import BaseModelError +from .....messaging.schemas.util import (SCHEMA_SENT_RECORD_TYPE, + notify_schema_event) +from .....multitenant.base import BaseMultitenantManager +from .....protocols.endorse_transaction.v1_0.manager import ( + TransactionManager, TransactionManagerError) +from .....protocols.endorse_transaction.v1_0.util import ( + get_endorser_connection_id, is_author_role) +from .....revocation.error import RevocationError +from .....revocation.indy import IndyRevocation +from .....storage.base import BaseStorage +from .....storage.error import StorageError, StorageNotFoundError +from ....anoncreds.issuer import AnonCredsIssuer, AnonCredsIssuerError +from ...anoncreds_registry import BaseRegistry +from ...models import (AnonCredsRegistryGetCredentialDefinition, + AnonCredsRegistryGetRevocationList, + AnonCredsRegistryGetRevocationRegistryDefinition, + AnonCredsRegistryGetSchema) +LOGGER = logging.getLogger(__name__) class DIDIndyRegistry(BaseAnonCredsResolver, BaseAnonCredsRegistrar): """DIDIndyRegistry""" @@ -20,31 +43,263 @@ async def setup(self, context: InjectionContext): """Setup.""" print("Successfully registered DIDIndyRegistry") - async def get_schema(self, schema_id: str) -> AnonCredsRegistryGetSchema: + async def get_schema(self, profile: Profile, schema_id: str) -> AnonCredsRegistryGetSchema: """Get a schema from the registry.""" + multitenant_mgr = profile.inject_or(BaseMultitenantManager) + if multitenant_mgr: + ledger_exec_inst = IndyLedgerRequestsExecutor(profile) + else: + ledger_exec_inst = profile.inject(IndyLedgerRequestsExecutor) + ledger_id, ledger = await ledger_exec_inst.get_ledger_for_identifier( + schema_id, + txn_record_type=GET_SCHEMA, + ) + if not ledger: + reason = "No ledger available" + if not profile.settings.get_value("wallet.type"): + reason += ": missing wallet-type?" + raise # TODO: create AnonCreds error + + async with ledger: + try: + schema = await ledger.get_schema(schema_id) + # TODO: use schema to create AnonCredsSchema and AnonCredsRegistryGetSchema objects + # ledger_id goes in resolution_metadata + except LedgerError as err: + raise # TODO: create AnonCreds error + return schema + # TODO: job_id? + + async def get_schemas(self, profile: Profile, filter: str): + """Get schema ids filtered by filter""" + # TODO: determine keyword arguments - async def register_schema(self): + async def register_schema( + self, + profile: Profile, + schema_name: str, + schema_version: str, + attributes: dict, + issuer_id: str, + outbound_handler, + ): """Register a schema on the registry.""" + # TODO: need issuer_id to + # issuer_id needs to sign the transaction too + + # Check that schema doesn't already exist + tag_query = {"schema_name": schema_name, "schema_version": schema_version} + async with profile.session() as session: + storage = session.inject(BaseStorage) + found = await storage.find_all_records( + type_filter=SCHEMA_SENT_RECORD_TYPE, + tag_query=tag_query, + ) + if 0 < len(found): + raise # Anoncreds error: f"Schema {schema_name} {schema_version} already exists" + + # Assume endorser role on the network + # No option for 3rd-party endorser + + ledger = profile.inject_or(BaseLedger) + if not ledger: + reason = "No ledger available" + if not profile.settings.get_value("wallet.type"): + reason += ": missing wallet-type?" + raise # Anoncreds error | web.HTTPForbidden(reason=reason) + + issuer = profile.inject(AnonCredsIssuer) + async with ledger: + try: + # if create_transaction_for_endorser, then the returned "schema_def" + # is actually the signed transaction + schema_id, schema_def = await shield( + ledger.create_and_send_schema( + issuer, + schema_name, + schema_version, + attributes, + write_ledger=True, # TODO: check + endorser_did=issuer_id, + ) + ) + except (AnonCredsIssuerError, LedgerError) as err: + raise # Anoncreds error | web.HTTPBadRequest(reason=err.roll_up) from err + + # TODO: use AnonCredsSchema object? + meta_data = { + "context": { + "schema_id": schema_id, + "schema_name": schema_name, + "schema_version": schema_version, + "attributes": attributes, + }, + "processing": {}, + } + + # Notify event + await notify_schema_event(profile, schema_id, meta_data) + return ( # TODO: check + { + "sent": {"schema_id": schema_id, "schema": schema_def}, + "schema_id": schema_id, + "schema": schema_def, + } + ) + + # TODO: job_id? + async def get_credential_definition( - self, credential_definition_id: str + self, profile: Profile, cred_def_id: str ) -> AnonCredsRegistryGetCredentialDefinition: """Get a credential definition from the registry.""" + async with profile.session() as session: + multitenant_mgr = session.inject_or(BaseMultitenantManager) + if multitenant_mgr: + ledger_exec_inst = IndyLedgerRequestsExecutor(profile) + else: + ledger_exec_inst = session.inject(IndyLedgerRequestsExecutor) + ledger_id, ledger = await ledger_exec_inst.get_ledger_for_identifier( + cred_def_id, + txn_record_type=GET_CRED_DEF, + ) + if not ledger: + reason = "No ledger available" + if not profile.settings.get_value("wallet.type"): + reason += ": missing wallet-type?" + raise # Anoncreds error | web.HTTPForbidden(reason=reason) + + async with ledger: + cred_def = await ledger.get_credential_definition(cred_def_id) + + return cred_def + + # job_id + + # TODO: determine keyword arguments - async def register_credential_definition(self): + async def register_credential_definition( + self, + profile: Profile, + schema_id: str, + support_revocation: bool, + tag: str, + rev_reg_size: int, + issuer_id: str, + ): """Register a credential definition on the registry.""" + tag_query = {"schema_id": schema_id} + async with profile.session() as session: + storage = session.inject(BaseStorage) + found = await storage.find_all_records( + type_filter=CRED_DEF_SENT_RECORD_TYPE, + tag_query=tag_query, + ) + if 0 < len(found): + # need to check the 'tag' value + for record in found: + cred_def_id = record.value + cred_def_id_parts = cred_def_id.split(":") + if tag == cred_def_id_parts[4]: + raise # Anoncreds error: web.HTTPBadRequest( + # reason=f"Cred def for {schema_id} {tag} already exists" + # ) + + ledger = profile.inject_or(BaseLedger) + if not ledger: + reason = "No ledger available" + if not profile.settings.get_value("wallet.type"): + reason += ": missing wallet-type?" + raise # Anoncreds error web.HTTPForbidden(reason=reason) + + issuer = profile.inject(AnonCredsIssuer) + try: # even if in wallet, send it and raise if erroneously so + async with ledger: + (cred_def_id, cred_def, novel) = await shield( + ledger.create_and_send_credential_definition( + issuer, + schema_id, + signature_type=None, + tag=tag, + support_revocation=support_revocation, + write_ledger=True, # TODO: check + endorser_did=issuer_id, + ) + ) + + except (AnonCredsIssuerError, LedgerError) as e: + raise # Anoncreds error web.HTTPBadRequest(reason=e.message) from e + + issuer_did = cred_def_id.split(":")[0] + meta_data = { + "context": { + "schema_id": schema_id, + "cred_def_id": cred_def_id, + "issuer_did": issuer_did, + "support_revocation": support_revocation, + "novel": novel, + "tag": tag, + "rev_reg_size": rev_reg_size, + }, + "processing": { + "create_pending_rev_reg": True, + }, + } + + # Notify event + meta_data["processing"]["auto_create_rev_reg"] = True + await notify_cred_def_event(profile, cred_def_id, meta_data) + + return ( # TODO: check + { + "sent": {"credential_definition_id": cred_def_id}, + "credential_definition_id": cred_def_id, + } + ) + + async def get_revocation_registry_definition( - self, revocation_registry_id: str + self, profile: Profile, rev_reg_id: str ) -> AnonCredsRegistryGetRevocationRegistryDefinition: """Get a revocation registry definition from the registry.""" + try: + revoc = IndyRevocation(profile) + rev_reg = await revoc.get_issuer_rev_reg_record(rev_reg_id) + except StorageNotFoundError as err: + raise # Anoncreds error web.HTTPNotFound(reason=err.roll_up) from err + + return rev_reg.serialize + # use AnonCredsRevocationRegistryDefinition object + # TODO: determine keyword arguments - async def register_revocation_registry_definition(self): + async def register_revocation_registry_definition( + self, + profile: Profile, + rev_reg_id: str, + issuer_id: str, + ): """Register a revocation registry definition on the registry.""" + try: + revoc = IndyRevocation(profile) + rev_reg = await revoc.get_issuer_rev_reg_record(rev_reg_id) + + rev_reg_resp = await rev_reg.send_def( + profile, + write_ledger=True, + endorser_did=issuer_id, + ) + LOGGER.debug("published rev reg definition: %s", rev_reg_id) + except StorageNotFoundError as err: + raise # Anoncreds error web.HTTPNotFound(reason=err.roll_up) from err + except RevocationError as err: + raise # Anoncreds error web.HTTPBadRequest(reason=err.roll_up) from err + async def get_revocation_list( self, revocation_registry_id: str, timestamp: str ) -> AnonCredsRegistryGetRevocationList: From 06d71817480f097c38ef4acfa36b61de568ccb28 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Thu, 16 Mar 2023 16:22:21 -0600 Subject: [PATCH 050/150] fix: imports Signed-off-by: Char Howland --- .../default/did_indy_registry/registry.py | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py index c515f826d5..48122fa903 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py @@ -3,7 +3,6 @@ from asyncio import shield from .....config.injection_context import InjectionContext -from .....connections.models.conn_record import ConnRecord from .....core.profile import Profile from .....ledger.base import BaseLedger from .....ledger.error import LedgerError @@ -11,24 +10,20 @@ GET_CRED_DEF, GET_SCHEMA, IndyLedgerRequestsExecutor) from .....messaging.credential_definitions.util import ( CRED_DEF_SENT_RECORD_TYPE, notify_cred_def_event) -from .....messaging.models.base import BaseModelError from .....messaging.schemas.util import (SCHEMA_SENT_RECORD_TYPE, notify_schema_event) from .....multitenant.base import BaseMultitenantManager -from .....protocols.endorse_transaction.v1_0.manager import ( - TransactionManager, TransactionManagerError) -from .....protocols.endorse_transaction.v1_0.util import ( - get_endorser_connection_id, is_author_role) from .....revocation.error import RevocationError from .....revocation.indy import IndyRevocation from .....storage.base import BaseStorage -from .....storage.error import StorageError, StorageNotFoundError -from ....anoncreds.issuer import AnonCredsIssuer, AnonCredsIssuerError -from ...anoncreds_registry import BaseRegistry -from ...models import (AnonCredsRegistryGetCredentialDefinition, - AnonCredsRegistryGetRevocationList, - AnonCredsRegistryGetRevocationRegistryDefinition, - AnonCredsRegistryGetSchema) +from .....storage.error import StorageNotFoundError +from ....issuer import AnonCredsIssuer, AnonCredsIssuerError +from ....models.anoncreds_cred_def import ( + AnonCredsRegistryGetCredentialDefinition, + AnonCredsRegistryGetRevocationList, + AnonCredsRegistryGetRevocationRegistryDefinition) +from ....models.anoncreds_schema import AnonCredsRegistryGetSchema +from ...base_registry import BaseAnonCredsRegistrar, BaseAnonCredsResolver LOGGER = logging.getLogger(__name__) From 14d39919ad88bb7aca4b60255ca0b9b09e076449 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Thu, 16 Mar 2023 22:35:29 -0600 Subject: [PATCH 051/150] fix: pass options, schema to AnonCredsRegistry.register_schema Signed-off-by: Char Howland --- .../anoncreds/anoncreds/anoncreds_registry.py | 2 +- aries_cloudagent/anoncreds/anoncreds/routes.py | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py b/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py index b9cf1e8529..a770691e53 100644 --- a/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py @@ -67,7 +67,7 @@ async def get_schema(self, schema_id: str) -> AnonCredsRegistryGetSchema: raise AnonCredsObjectNotFound(f"{schema_id} could not be resolved") # TODO: determine keyword arguments - async def register_schema(self): + async def register_schema(self, options, schema): """Register a schema on the registry.""" for registrar in await self._registrars_for_identifiers("something"): try: diff --git a/aries_cloudagent/anoncreds/anoncreds/routes.py b/aries_cloudagent/anoncreds/anoncreds/routes.py index 41d4ef9564..13b57b576e 100644 --- a/aries_cloudagent/anoncreds/anoncreds/routes.py +++ b/aries_cloudagent/anoncreds/anoncreds/routes.py @@ -195,9 +195,19 @@ async def schemas_post(request: web.BaseRequest): options = request_data.get("option") schema_data = request_data.get("schema") +<<<<<<< HEAD schema: AnonCredsSchema = AnonCredsSchema.deserialize(schema_data) #TODO: serialize return stuff. result = anon_creds_registry.register_schema(options, schema) +======= + schema: AnonCredsSchema = AnonCredsSchema( + issuer_id=schema_data.get("issuerId"), + attr_names=schema_data.get("attrNames"), + name=schema_data.get("name"), + version=schema_data.get("version"), + ) + result = await anon_creds_registry.register_schema(options,schema) +>>>>>>> fix: pass options, schema to AnonCredsRegistry.register_schema return web.json_response(result) From 0d028e8b2f2907b4421cc9997f122a882b653e76 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Fri, 17 Mar 2023 10:47:07 -0600 Subject: [PATCH 052/150] feat: define supported_identifiers_regex on registries Signed-off-by: Char Howland --- .../anoncreds/anoncreds/anoncreds_registry.py | 13 ++----- .../default/did_indy_registry/registry.py | 37 ++++++++++--------- .../default/did_web_registry/registry.py | 10 ++++- .../default/legacy_indy_registry/registry.py | 11 +++++- .../anoncreds/anoncreds/routes.py | 10 ----- 5 files changed, 41 insertions(+), 40 deletions(-) diff --git a/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py b/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py index a770691e53..0001640736 100644 --- a/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py @@ -16,7 +16,7 @@ LOGGER = logging.getLogger(__name__) -class AnonCredsRegistry(BaseAnonCredsResolver, BaseAnonCredsRegistrar): +class AnonCredsRegistry(): """AnonCredsRegistry""" def __init__(self, registries: Optional[List[BaseAnonCredsHandler]] = None): @@ -27,11 +27,6 @@ def __init__(self, registries: Optional[List[BaseAnonCredsHandler]] = None): for registry in registries: self.register(registry) - @property - def supported_identifiers_regex(self): - """Return supported identifiers.""" - return "" #TODO: implement me - def register(self, registry: BaseAnonCredsHandler): """Register a new registry.""" if isinstance(registry, BaseAnonCredsResolver): @@ -67,11 +62,11 @@ async def get_schema(self, schema_id: str) -> AnonCredsRegistryGetSchema: raise AnonCredsObjectNotFound(f"{schema_id} could not be resolved") # TODO: determine keyword arguments - async def register_schema(self, options, schema): + async def register_schema(self, profile, options, schema): """Register a schema on the registry.""" - for registrar in await self._registrars_for_identifiers("something"): + for registrar in await self._registrars_for_identifiers(schema.issuer_id): try: - return await registrar.register_schema() + return await registrar.register_schema(profile, options, schema) except BaseAnonCredsError: LOGGER.exception("Error registering schema with registrar") diff --git a/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py index 48122fa903..804ea7d6fa 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py @@ -1,6 +1,8 @@ """DID Indy Registry""" import logging from asyncio import shield +import re +from typing import Pattern from .....config.injection_context import InjectionContext from .....core.profile import Profile @@ -30,17 +32,21 @@ class DIDIndyRegistry(BaseAnonCredsResolver, BaseAnonCredsRegistrar): """DIDIndyRegistry""" + def __init__(self): + self._supported_identifiers_regex = re.compile(r"^did:indy:.*$") + @property - def supported_identifiers_regex(self): - return "" # TODO: implement me + def supported_identifiers_regex(self) -> Pattern: + return self._supported_identifiers_regex + # TODO: fix regex (too general) async def setup(self, context: InjectionContext): """Setup.""" print("Successfully registered DIDIndyRegistry") - async def get_schema(self, profile: Profile, schema_id: str) -> AnonCredsRegistryGetSchema: + async def get_schema(self, profile: Profile, options, schema, issuer_id) -> AnonCredsRegistryGetSchema: """Get a schema from the registry.""" - + schema_id = schema.schema_id multitenant_mgr = profile.inject_or(BaseMultitenantManager) if multitenant_mgr: ledger_exec_inst = IndyLedgerRequestsExecutor(profile) @@ -73,18 +79,15 @@ async def get_schemas(self, profile: Profile, filter: str): async def register_schema( self, profile: Profile, - schema_name: str, - schema_version: str, - attributes: dict, - issuer_id: str, - outbound_handler, + options: dict, + schema, ): """Register a schema on the registry.""" # TODO: need issuer_id to # issuer_id needs to sign the transaction too # Check that schema doesn't already exist - tag_query = {"schema_name": schema_name, "schema_version": schema_version} + tag_query = {"schema_name": schema.name, "schema_version": schema.version} async with profile.session() as session: storage = session.inject(BaseStorage) found = await storage.find_all_records( @@ -112,11 +115,11 @@ async def register_schema( schema_id, schema_def = await shield( ledger.create_and_send_schema( issuer, - schema_name, - schema_version, - attributes, + schema.name, + schema.version, + schema.attr_names, write_ledger=True, # TODO: check - endorser_did=issuer_id, + endorser_did=schema.issuer_id, ) ) except (AnonCredsIssuerError, LedgerError) as err: @@ -126,9 +129,9 @@ async def register_schema( meta_data = { "context": { "schema_id": schema_id, - "schema_name": schema_name, - "schema_version": schema_version, - "attributes": attributes, + "schema_name": schema.name, + "schema_version": schema.version, + "attributes": schema.attr_names, }, "processing": {}, } diff --git a/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py index ded3aad98d..60cfcd673a 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py @@ -1,4 +1,6 @@ """DID Web Registry""" +import re +from typing import Pattern from ....models.anoncreds_cred_def import ( AnonCredsRegistryGetCredentialDefinition, AnonCredsRegistryGetRevocationList, @@ -12,9 +14,13 @@ class DIDWebRegistry(BaseAnonCredsResolver, BaseAnonCredsRegistrar): """DIDWebRegistry""" + def __init__(self): + self._supported_identifiers_regex = re.compile(r"^did:web:.*$") + @property - def supported_identifiers_regex(self): - return "" # TODO: implement me + def supported_identifiers_regex(self) -> Pattern: + return self._supported_identifiers_regex + # TODO: fix regex (too general) async def setup(self, context: InjectionContext): """Setup.""" diff --git a/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py index a6fad3dc2f..327a15a627 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py @@ -1,4 +1,7 @@ """Legacy Indy Registry""" +import re +from typing import Pattern + from ....models.anoncreds_cred_def import ( AnonCredsRegistryGetCredentialDefinition, AnonCredsRegistryGetRevocationList, @@ -12,9 +15,13 @@ class LegacyIndyRegistry(BaseAnonCredsResolver, BaseAnonCredsRegistrar): """LegacyIndyRegistry""" + def __init__(self): + self._supported_identifiers_regex = re.compile(r"^(?!did).*$") + @property - def supported_identifiers_regex(self): - return "" # TODO: implement me + def supported_identifiers_regex(self) -> Pattern: + return self._supported_identifiers_regex + # TODO: fix regex (too general) async def setup(self, context: InjectionContext): """Setup.""" diff --git a/aries_cloudagent/anoncreds/anoncreds/routes.py b/aries_cloudagent/anoncreds/anoncreds/routes.py index 13b57b576e..41d4ef9564 100644 --- a/aries_cloudagent/anoncreds/anoncreds/routes.py +++ b/aries_cloudagent/anoncreds/anoncreds/routes.py @@ -195,19 +195,9 @@ async def schemas_post(request: web.BaseRequest): options = request_data.get("option") schema_data = request_data.get("schema") -<<<<<<< HEAD schema: AnonCredsSchema = AnonCredsSchema.deserialize(schema_data) #TODO: serialize return stuff. result = anon_creds_registry.register_schema(options, schema) -======= - schema: AnonCredsSchema = AnonCredsSchema( - issuer_id=schema_data.get("issuerId"), - attr_names=schema_data.get("attrNames"), - name=schema_data.get("name"), - version=schema_data.get("version"), - ) - result = await anon_creds_registry.register_schema(options,schema) ->>>>>>> fix: pass options, schema to AnonCredsRegistry.register_schema return web.json_response(result) From 4539efca480e5a1fb587d82a6d9427ff47aa3f51 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Fri, 17 Mar 2023 13:47:00 -0600 Subject: [PATCH 053/150] fix: register_schema signature Signed-off-by: Char Howland --- .../default/did_web_registry/registry.py | 16 +++++++++++----- .../default/legacy_indy_registry/registry.py | 15 ++++++++++----- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py index 60cfcd673a..148413dde7 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py @@ -1,14 +1,15 @@ """DID Web Registry""" import re from typing import Pattern + +from .....config.injection_context import InjectionContext +from .....core.profile import Profile from ....models.anoncreds_cred_def import ( AnonCredsRegistryGetCredentialDefinition, AnonCredsRegistryGetRevocationList, - AnonCredsRegistryGetRevocationRegistryDefinition, -) + AnonCredsRegistryGetRevocationRegistryDefinition) from ....models.anoncreds_schema import AnonCredsRegistryGetSchema -from .....config.injection_context import InjectionContext -from ...base_registry import BaseAnonCredsResolver, BaseAnonCredsRegistrar +from ...base_registry import BaseAnonCredsRegistrar, BaseAnonCredsResolver class DIDWebRegistry(BaseAnonCredsResolver, BaseAnonCredsRegistrar): @@ -30,7 +31,12 @@ async def get_schema(self, schema_id: str) -> AnonCredsRegistryGetSchema: """Get a schema from the registry.""" # TODO: determine keyword arguments - async def register_schema(self): + async def register_schema( + self, + profile: Profile, + options: dict, + schema, + ): """Register a schema on the registry.""" async def get_credential_definition( diff --git a/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py index 327a15a627..8eb8d06e27 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py @@ -2,14 +2,14 @@ import re from typing import Pattern +from .....config.injection_context import InjectionContext +from .....core.profile import Profile from ....models.anoncreds_cred_def import ( AnonCredsRegistryGetCredentialDefinition, AnonCredsRegistryGetRevocationList, - AnonCredsRegistryGetRevocationRegistryDefinition, -) + AnonCredsRegistryGetRevocationRegistryDefinition) from ....models.anoncreds_schema import AnonCredsRegistryGetSchema -from .....config.injection_context import InjectionContext -from ...base_registry import BaseAnonCredsResolver, BaseAnonCredsRegistrar +from ...base_registry import BaseAnonCredsRegistrar, BaseAnonCredsResolver class LegacyIndyRegistry(BaseAnonCredsResolver, BaseAnonCredsRegistrar): @@ -31,7 +31,12 @@ async def get_schema(self, schema_id: str) -> AnonCredsRegistryGetSchema: """Get a schema from the registry.""" # TODO: determine keyword arguments - async def register_schema(self): + async def register_schema( + self, + profile: Profile, + options: dict, + schema, + ): """Register a schema on the registry.""" async def get_credential_definition( From e77122de440ae07bcee245a525d54febcb959e4d Mon Sep 17 00:00:00 2001 From: Char Howland Date: Fri, 17 Mar 2023 13:47:21 -0600 Subject: [PATCH 054/150] feat: did:indy register_schema response Signed-off-by: Char Howland --- .../default/did_indy_registry/registry.py | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py index 804ea7d6fa..e87a79a8cb 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py @@ -138,16 +138,25 @@ async def register_schema( # Notify event await notify_schema_event(profile, schema_id, meta_data) - return ( # TODO: check - { - "sent": {"schema_id": schema_id, "schema": schema_def}, + + return { + "job_id": None, + "schema_state": { + "state": "finished", "schema_id": schema_id, - "schema": schema_def, + "schema": { + "attrNames": schema_def["attrNames"], + "name": schema_def["name"], + "version": schema_def["ver"], + "issuerId": schema.issuer_id + } + }, + "registration_metadata": {}, + # For indy, schema_metadata will contain the seqNo + "schema_metadata": { + "seqNo": schema_def["seqNo"] } - ) - - # TODO: job_id? - + } async def get_credential_definition( self, profile: Profile, cred_def_id: str From fed1b478c8e42445d5af9ef9d94307485b86a8cb Mon Sep 17 00:00:00 2001 From: Char Howland Date: Fri, 17 Mar 2023 14:28:04 -0600 Subject: [PATCH 055/150] fix: await and pass profile to register_schema() Signed-off-by: Char Howland --- aries_cloudagent/anoncreds/anoncreds/routes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aries_cloudagent/anoncreds/anoncreds/routes.py b/aries_cloudagent/anoncreds/anoncreds/routes.py index 41d4ef9564..a250c889d0 100644 --- a/aries_cloudagent/anoncreds/anoncreds/routes.py +++ b/aries_cloudagent/anoncreds/anoncreds/routes.py @@ -197,7 +197,7 @@ async def schemas_post(request: web.BaseRequest): schema: AnonCredsSchema = AnonCredsSchema.deserialize(schema_data) #TODO: serialize return stuff. - result = anon_creds_registry.register_schema(options, schema) + result = await anon_creds_registry.register_schema(context.profile, options, schema) return web.json_response(result) From 5ae0acbdafdbad59e09946bd089d026cb7a93638 Mon Sep 17 00:00:00 2001 From: Adam Burdett Date: Mon, 20 Mar 2023 16:11:49 -0600 Subject: [PATCH 056/150] get resources models and interface Signed-off-by: Adam Burdett --- .../anoncreds/anoncreds/anoncreds_registry.py | 20 +++++++ .../anoncreds/anoncreds/base_registry.py | 21 ++++++- .../default/did_indy_registry/registry.py | 5 ++ .../default/did_web_registry/registry.py | 9 +++ .../default/legacy_indy_registry/registry.py | 9 +++ .../anoncreds/anoncreds/routes.py | 7 ++- .../anoncreds/models/anoncreds_cred_def.py | 60 +++++++++++++++++++ .../anoncreds/models/anoncreds_schema.py | 24 +++++++- 8 files changed, 148 insertions(+), 7 deletions(-) diff --git a/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py b/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py index 0001640736..c3e337a410 100644 --- a/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py @@ -1,4 +1,5 @@ """AnonCreds Registry""" +import itertools import logging from typing import List, Optional @@ -61,6 +62,15 @@ async def get_schema(self, schema_id: str) -> AnonCredsRegistryGetSchema: raise AnonCredsObjectNotFound(f"{schema_id} could not be resolved") + async def get_schemas(self,filter : dict): + """Get schema id's from the registry.""" + results = [ + resolver.get_schemas(filter) + for resolver in self.resolvers + ] + return itertools.chain.from_iterable(results) + + # TODO: determine keyword arguments async def register_schema(self, profile, options, schema): """Register a schema on the registry.""" @@ -87,6 +97,16 @@ async def get_credential_definition( raise AnonCredsObjectNotFound( f"{credential_definition_id} could not be resolved" ) + + async def get_credential_definitions( + self, filter : dict + ): + """Get credential definitions id's from the registry.""" + results = [ + resolver.get(filter) + for resolver in self.resolvers + ] + return itertools.chain.from_iterable(results) # TODO: determine keyword arguments async def register_credential_definition(self): diff --git a/aries_cloudagent/anoncreds/anoncreds/base_registry.py b/aries_cloudagent/anoncreds/anoncreds/base_registry.py index 77715fba62..1cb512ed43 100644 --- a/aries_cloudagent/anoncreds/anoncreds/base_registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/base_registry.py @@ -7,8 +7,10 @@ AnonCredsRegistryGetCredentialDefinition, AnonCredsRegistryGetRevocationList, AnonCredsRegistryGetRevocationRegistryDefinition, + AnonCredsRegistryGetCredentialDefinitions, + AnonCredsRegistryGetRevocationRegistryDefinitions, ) -from ..models.anoncreds_schema import AnonCredsRegistryGetSchema +from ..models.anoncreds_schema import AnonCredsRegistryGetSchema, AnonCredsRegistryGetSchemas class BaseAnonCredsError(Exception): @@ -42,18 +44,34 @@ class BaseAnonCredsResolver(BaseAnonCredsHandler): @abstractmethod async def get_schema(self, schema_id: str) -> AnonCredsRegistryGetSchema: """Get a schema from the registry.""" + + @abstractmethod + async def get_schemas(self, filter: dict) -> AnonCredsRegistryGetSchemas: + """Get a schema ids from the registry.""" @abstractmethod async def get_credential_definition( self, credential_definition_id: str ) -> AnonCredsRegistryGetCredentialDefinition: """Get a credential definition from the registry.""" + + @abstractmethod + async def get_credential_definitions( + self, filter: dict + ) -> AnonCredsRegistryGetCredentialDefinitions: + """Get a credential definition ids from the registry.""" @abstractmethod async def get_revocation_registry_definition( self, revocation_registry_id: str ) -> AnonCredsRegistryGetRevocationRegistryDefinition: """Get a revocation registry definition from the registry.""" + + @abstractmethod + async def get_revocation_registry_definitions( + self, filter: dict + ) -> AnonCredsRegistryGetRevocationRegistryDefinitions: + """Get a revocation registry definition ids from the registry.""" @abstractmethod async def get_revocation_list( @@ -62,6 +80,7 @@ async def get_revocation_list( """Get a revocation list from the registry.""" + class BaseAnonCredsRegistrar(BaseAnonCredsHandler): # TODO: determine keyword arguments diff --git a/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py index e87a79a8cb..e9845bfbc7 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py @@ -186,6 +186,8 @@ async def get_credential_definition( # job_id + async def get_credential_definitions(self, profile: Profile, filter: str): + """Get credential definition ids filtered by filter""" # TODO: determine keyword arguments async def register_credential_definition( @@ -307,6 +309,9 @@ async def register_revocation_registry_definition( except RevocationError as err: raise # Anoncreds error web.HTTPBadRequest(reason=err.roll_up) from err + async def get_revocation_registry_definitions(self, profile: Profile, filter: str): + """Get credential definition ids filtered by filter""" + async def get_revocation_list( self, revocation_registry_id: str, timestamp: str ) -> AnonCredsRegistryGetRevocationList: diff --git a/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py index 148413dde7..c6021a770d 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py @@ -30,6 +30,9 @@ async def setup(self, context: InjectionContext): async def get_schema(self, schema_id: str) -> AnonCredsRegistryGetSchema: """Get a schema from the registry.""" + async def get_schemas(self, profile: Profile, filter: str): + """Get schema ids filtered by filter""" + # TODO: determine keyword arguments async def register_schema( self, @@ -44,6 +47,9 @@ async def get_credential_definition( ) -> AnonCredsRegistryGetCredentialDefinition: """Get a credential definition from the registry.""" + async def get_credential_definitions(self, profile: Profile, filter: str): + """Get credential definition ids filtered by filter""" + # TODO: determine keyword arguments async def register_credential_definition(self): """Register a credential definition on the registry.""" @@ -57,6 +63,9 @@ async def get_revocation_registry_definition( async def register_revocation_registry_definition(self): """Register a revocation registry definition on the registry.""" + async def get_revocation_registry_definitions(self, profile: Profile, filter: str): + """Get credential definition ids filtered by filter""" + async def get_revocation_list( self, revocation_registry_id: str, timestamp: str ) -> AnonCredsRegistryGetRevocationList: diff --git a/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py index 8eb8d06e27..354cd37b2c 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py @@ -30,6 +30,9 @@ async def setup(self, context: InjectionContext): async def get_schema(self, schema_id: str) -> AnonCredsRegistryGetSchema: """Get a schema from the registry.""" + async def get_schemas(self, profile: Profile, filter: str): + """Get schema ids filtered by filter""" + # TODO: determine keyword arguments async def register_schema( self, @@ -44,6 +47,9 @@ async def get_credential_definition( ) -> AnonCredsRegistryGetCredentialDefinition: """Get a credential definition from the registry.""" + async def get_credential_definitions(self, profile: Profile, filter: str): + """Get credential definition ids filtered by filter""" + # TODO: determine keyword arguments async def register_credential_definition(self): """Register a credential definition on the registry.""" @@ -53,6 +59,9 @@ async def get_revocation_registry_definition( ) -> AnonCredsRegistryGetRevocationRegistryDefinition: """Get a revocation registry definition from the registry.""" + async def get_revocation_registry_definitions(self, profile: Profile, filter: str): + """Get credential definition ids filtered by filter""" + # TODO: determine keyword arguments async def register_revocation_registry_definition(self): """Register a revocation registry definition on the registry.""" diff --git a/aries_cloudagent/anoncreds/anoncreds/routes.py b/aries_cloudagent/anoncreds/anoncreds/routes.py index a250c889d0..ce26191b76 100644 --- a/aries_cloudagent/anoncreds/anoncreds/routes.py +++ b/aries_cloudagent/anoncreds/anoncreds/routes.py @@ -18,12 +18,13 @@ ) from aries_cloudagent.anoncreds.models.anoncreds_schema import ( + AnonCredsRegistryGetSchemasSchema, AnonCredsSchema, PostSchemaResponseSchema, SchemaPostQueryStringSchema, AnonCredsRegistryGetSchemaSchema, SchemasQueryStringSchema, - SchemasResponseSchema, + AnonCredsRegistryGetSchemas, ) from aries_cloudagent.anoncreds.models.anoncreds_valid import ( ANONCREDS_SCHEMA_ID, @@ -196,7 +197,7 @@ async def schemas_post(request: web.BaseRequest): schema_data = request_data.get("schema") schema: AnonCredsSchema = AnonCredsSchema.deserialize(schema_data) - #TODO: serialize return stuff. + # TODO: serialize return stuff. result = await anon_creds_registry.register_schema(context.profile, options, schema) return web.json_response(result) @@ -224,7 +225,7 @@ async def schema_get(request: web.BaseRequest): @docs(tags=["anoncreds"], summary="") @querystring_schema(SchemasQueryStringSchema()) -@response_schema(SchemasResponseSchema(), 200, description="") +@response_schema(AnonCredsRegistryGetSchemasSchema(), 200, description="") async def schemas_get(request: web.BaseRequest): """Request handler for getting all schemas. diff --git a/aries_cloudagent/anoncreds/models/anoncreds_cred_def.py b/aries_cloudagent/anoncreds/models/anoncreds_cred_def.py index 97a2b972b1..68e9b42ba2 100644 --- a/aries_cloudagent/anoncreds/models/anoncreds_cred_def.py +++ b/aries_cloudagent/anoncreds/models/anoncreds_cred_def.py @@ -161,6 +161,37 @@ def __init__( self.credential_definition_metadata = credential_definition_metadata +class AnonCredsRegistryGetCredentialDefinitions(BaseModel): + """AnonCredsRegistryGetCredentialDefinitions""" + + class Meta: + """AnonCredsRegistryGetCredentialDefinitions metadata.""" + + schema_class = "AnonCredsRegistryGetCredentialDefinitionsSchema" + + def __init__(self, credential_definition_ids: list, **kwargs): + super().__init__(**kwargs) + self.credential_definition_ids = credential_definition_ids + + +class AnonCredsRegistryGetCredentialDefinitionsSchema(BaseModelSchema): + """AnonCredsRegistryGetCredentialDefinitionsSchema""" + + class Meta: + """AnonCredsRegistryGetCredentialDefinitionsSchema metadata""" + + model_class = AnonCredsRegistryGetCredentialDefinitions + unknown = EXCLUDE + + credential_definition_ids = fields.List( + fields.Str( + data_key="credentialDefinitionIds", + description="credential definition identifiers", + **INDY_CRED_DEF_ID, + ) + ) + + class AnonCredsRegistryGetCredentialDefinitionSchema(BaseModelSchema): """Parameters and validators for credential definition list response.""" @@ -275,6 +306,35 @@ class Meta: revocation_registry_metadata = fields.Dict() +class AnonCredsRegistryGetRevocationRegistryDefinitions(BaseModel): + """AnonCredsRegistryGetRevocationRegistryDefinitions""" + + class Meta: + """AnonCredsRegistryGetRevocationRegistryDefinitions metadata.""" + + schema_class = "AnonCredsRegistryGetRevocationRegistryDefinitionsSchema" + + def __init__(self, revocation_definition_ids: list, **kwargs): + super().__init__(**kwargs) + self.revocation_definition_ids = revocation_definition_ids + +class AnonCredsRegistryGetRevocationRegistryDefinitionsSchema(BaseModelSchema): + """AnonCredsRegistryGetRevocationRegistryDefinitionsSchema""" + + class Meta: + """AnonCredsRegistryGetRevocationRegistryDefinitionsSchema metadata""" + + model_class = AnonCredsRegistryGetRevocationRegistryDefinitions + unknown = EXCLUDE + + revocation_definition_ids = fields.List( + fields.Str( + data_key="revocation_definition_ids", + description="credential definition identifiers", + **INDY_CRED_DEF_ID, + ) + ) + class AnonCredsRevocationList(BaseModel): """AnonCredsRevocationList""" diff --git a/aries_cloudagent/anoncreds/models/anoncreds_schema.py b/aries_cloudagent/anoncreds/models/anoncreds_schema.py index ef278076b8..c995411252 100644 --- a/aries_cloudagent/anoncreds/models/anoncreds_schema.py +++ b/aries_cloudagent/anoncreds/models/anoncreds_schema.py @@ -109,13 +109,31 @@ class SchemaState(OpenAPISchema): ) schema_ = fields.Nested(AnonCredsSchemaSchema(), data_key="schema") +class AnonCredsRegistryGetSchemas(BaseModel): + """SchemasResponAnonCredsRegistryGetSchemasseSchema""" + class Meta: + """IndyCredInfo metadata.""" + + schema_class = "AnonCredsRegistryGetSchemasSchema" -class SchemasResponseSchema(OpenAPISchema): + def __init__( + self, + schema_ids: list, + **kwargs + ): + super().__init__(**kwargs) + self.schema_ids = schema_ids +class AnonCredsRegistryGetSchemasSchema(BaseModelSchema): """Parameters and validators for schema list all response.""" + class Meta: + """AnonCredsRegistryGetSchemasSchema metadata.""" + + model_class = AnonCredsRegistryGetSchemas + unknown = EXCLUDE - schema_id = fields.List( + schema_ids = fields.List( fields.Str( - data_key="schemaId", description="Schema identifier", **ANONCREDS_SCHEMA_ID + data_key="schemaIds", description="Schema identifier", **ANONCREDS_SCHEMA_ID ) ) From 8b0d25c61828973e7e5d5fac7df260ed956359ee Mon Sep 17 00:00:00 2001 From: Adam Burdett Date: Tue, 21 Mar 2023 12:42:12 -0600 Subject: [PATCH 057/150] get credential definitions Signed-off-by: Adam Burdett --- aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py b/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py index c3e337a410..594a25b57e 100644 --- a/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py @@ -103,7 +103,7 @@ async def get_credential_definitions( ): """Get credential definitions id's from the registry.""" results = [ - resolver.get(filter) + resolver.get_credential_definitions(filter) for resolver in self.resolvers ] return itertools.chain.from_iterable(results) From 48bd5a0b529cf05fc453ada55f44ea6f3e7c7851 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Wed, 22 Mar 2023 12:29:51 -0400 Subject: [PATCH 058/150] chore: update anoncreds python wrapper Signed-off-by: Daniel Bluhm --- requirements.anoncreds.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.anoncreds.txt b/requirements.anoncreds.txt index f4b03a781d..e684770d5f 100644 --- a/requirements.anoncreds.txt +++ b/requirements.anoncreds.txt @@ -1 +1 @@ -anoncreds@git+https://github.com/hyperledger/anoncreds-rs#subdirectory=wrappers/python +anoncreds@git+https://github.com/indicio-tech/anoncreds-rs@feature/python-rev-status-lists#subdirectory=wrappers/python From 49596ad0d30de89a53e5faec735e1befbbbd684a Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Wed, 22 Mar 2023 14:46:03 -0400 Subject: [PATCH 059/150] style: formatting fixes Signed-off-by: Daniel Bluhm --- .../anoncreds/anoncreds/__init__.py | 12 +-- .../anoncreds/anoncreds/anoncreds_registry.py | 36 ++++----- .../anoncreds/anoncreds/base_registry.py | 15 ++-- .../default/did_indy_registry/registry.py | 78 ++++++++++--------- .../default/did_web_registry/registry.py | 3 +- .../default/legacy_indy_registry/registry.py | 3 +- .../anoncreds/models/anoncreds_cred_def.py | 2 + .../anoncreds/models/anoncreds_schema.py | 11 +-- 8 files changed, 84 insertions(+), 76 deletions(-) diff --git a/aries_cloudagent/anoncreds/anoncreds/__init__.py b/aries_cloudagent/anoncreds/anoncreds/__init__.py index 6429951a70..36536b98d0 100644 --- a/aries_cloudagent/anoncreds/anoncreds/__init__.py +++ b/aries_cloudagent/anoncreds/anoncreds/__init__.py @@ -18,8 +18,8 @@ async def setup(context: InjectionContext): indy_registry = ClassProvider( "aries_cloudagent.anoncreds.anoncreds.default.did_indy_registry.registry" ".DIDIndyRegistry", - #supported_identifiers=[], - #method_name="did:indy", + # supported_identifiers=[], + # method_name="did:indy", ).provide(context.settings, context.injector) await indy_registry.setup(context) registry.register(indy_registry) @@ -27,8 +27,8 @@ async def setup(context: InjectionContext): web_registry = ClassProvider( "aries_cloudagent.anoncreds.anoncreds.default.did_web_registry.registry" ".DIDWebRegistry", - #supported_identifiers=[], - #method_name="did:web", + # supported_identifiers=[], + # method_name="did:web", ).provide(context.settings, context.injector) await web_registry.setup(context) registry.register(web_registry) @@ -36,8 +36,8 @@ async def setup(context: InjectionContext): legacy_indy_registry = ClassProvider( "aries_cloudagent.anoncreds.anoncreds.default.legacy_indy_registry.registry" ".LegacyIndyRegistry", - #supported_identifiers=[], - #method_name="", + # supported_identifiers=[], + # method_name="", ).provide(context.settings, context.injector) await legacy_indy_registry.setup(context) registry.register(legacy_indy_registry) diff --git a/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py b/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py index 594a25b57e..bb3f1e4244 100644 --- a/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py @@ -7,17 +7,22 @@ from ..models.anoncreds_cred_def import ( AnonCredsRegistryGetCredentialDefinition, AnonCredsRegistryGetRevocationList, - AnonCredsRegistryGetRevocationRegistryDefinition) + AnonCredsRegistryGetRevocationRegistryDefinition, +) from ..models.anoncreds_schema import AnonCredsRegistryGetSchema -from .base_registry import (AnonCredsObjectNotFound, - AnonCredsRegistrationFailed, BaseAnonCredsError, - BaseAnonCredsHandler, BaseAnonCredsRegistrar, - BaseAnonCredsResolver) +from .base_registry import ( + AnonCredsObjectNotFound, + AnonCredsRegistrationFailed, + BaseAnonCredsError, + BaseAnonCredsHandler, + BaseAnonCredsRegistrar, + BaseAnonCredsResolver, +) LOGGER = logging.getLogger(__name__) -class AnonCredsRegistry(): +class AnonCredsRegistry: """AnonCredsRegistry""" def __init__(self, registries: Optional[List[BaseAnonCredsHandler]] = None): @@ -27,7 +32,7 @@ def __init__(self, registries: Optional[List[BaseAnonCredsHandler]] = None): if registries: for registry in registries: self.register(registry) - + def register(self, registry: BaseAnonCredsHandler): """Register a new registry.""" if isinstance(registry, BaseAnonCredsResolver): @@ -62,14 +67,10 @@ async def get_schema(self, schema_id: str) -> AnonCredsRegistryGetSchema: raise AnonCredsObjectNotFound(f"{schema_id} could not be resolved") - async def get_schemas(self,filter : dict): + async def get_schemas(self, filter: dict): """Get schema id's from the registry.""" - results = [ - resolver.get_schemas(filter) - for resolver in self.resolvers - ] + results = [resolver.get_schemas(filter) for resolver in self.resolvers] return itertools.chain.from_iterable(results) - # TODO: determine keyword arguments async def register_schema(self, profile, options, schema): @@ -97,14 +98,11 @@ async def get_credential_definition( raise AnonCredsObjectNotFound( f"{credential_definition_id} could not be resolved" ) - - async def get_credential_definitions( - self, filter : dict - ): + + async def get_credential_definitions(self, filter: dict): """Get credential definitions id's from the registry.""" results = [ - resolver.get_credential_definitions(filter) - for resolver in self.resolvers + resolver.get_credential_definitions(filter) for resolver in self.resolvers ] return itertools.chain.from_iterable(results) diff --git a/aries_cloudagent/anoncreds/anoncreds/base_registry.py b/aries_cloudagent/anoncreds/anoncreds/base_registry.py index 1cb512ed43..4d17911e15 100644 --- a/aries_cloudagent/anoncreds/anoncreds/base_registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/base_registry.py @@ -10,7 +10,10 @@ AnonCredsRegistryGetCredentialDefinitions, AnonCredsRegistryGetRevocationRegistryDefinitions, ) -from ..models.anoncreds_schema import AnonCredsRegistryGetSchema, AnonCredsRegistryGetSchemas +from ..models.anoncreds_schema import ( + AnonCredsRegistryGetSchema, + AnonCredsRegistryGetSchemas, +) class BaseAnonCredsError(Exception): @@ -44,7 +47,7 @@ class BaseAnonCredsResolver(BaseAnonCredsHandler): @abstractmethod async def get_schema(self, schema_id: str) -> AnonCredsRegistryGetSchema: """Get a schema from the registry.""" - + @abstractmethod async def get_schemas(self, filter: dict) -> AnonCredsRegistryGetSchemas: """Get a schema ids from the registry.""" @@ -54,7 +57,7 @@ async def get_credential_definition( self, credential_definition_id: str ) -> AnonCredsRegistryGetCredentialDefinition: """Get a credential definition from the registry.""" - + @abstractmethod async def get_credential_definitions( self, filter: dict @@ -66,10 +69,10 @@ async def get_revocation_registry_definition( self, revocation_registry_id: str ) -> AnonCredsRegistryGetRevocationRegistryDefinition: """Get a revocation registry definition from the registry.""" - + @abstractmethod async def get_revocation_registry_definitions( - self, filter: dict + self, filter: dict ) -> AnonCredsRegistryGetRevocationRegistryDefinitions: """Get a revocation registry definition ids from the registry.""" @@ -80,9 +83,7 @@ async def get_revocation_list( """Get a revocation list from the registry.""" - class BaseAnonCredsRegistrar(BaseAnonCredsHandler): - # TODO: determine keyword arguments @abstractmethod async def register_schema(self): diff --git a/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py index e9845bfbc7..01ec34b411 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py @@ -9,11 +9,15 @@ from .....ledger.base import BaseLedger from .....ledger.error import LedgerError from .....ledger.multiple_ledger.ledger_requests_executor import ( - GET_CRED_DEF, GET_SCHEMA, IndyLedgerRequestsExecutor) + GET_CRED_DEF, + GET_SCHEMA, + IndyLedgerRequestsExecutor, +) from .....messaging.credential_definitions.util import ( - CRED_DEF_SENT_RECORD_TYPE, notify_cred_def_event) -from .....messaging.schemas.util import (SCHEMA_SENT_RECORD_TYPE, - notify_schema_event) + CRED_DEF_SENT_RECORD_TYPE, + notify_cred_def_event, +) +from .....messaging.schemas.util import SCHEMA_SENT_RECORD_TYPE, notify_schema_event from .....multitenant.base import BaseMultitenantManager from .....revocation.error import RevocationError from .....revocation.indy import IndyRevocation @@ -23,12 +27,14 @@ from ....models.anoncreds_cred_def import ( AnonCredsRegistryGetCredentialDefinition, AnonCredsRegistryGetRevocationList, - AnonCredsRegistryGetRevocationRegistryDefinition) + AnonCredsRegistryGetRevocationRegistryDefinition, +) from ....models.anoncreds_schema import AnonCredsRegistryGetSchema from ...base_registry import BaseAnonCredsRegistrar, BaseAnonCredsResolver LOGGER = logging.getLogger(__name__) + class DIDIndyRegistry(BaseAnonCredsResolver, BaseAnonCredsRegistrar): """DIDIndyRegistry""" @@ -44,7 +50,9 @@ async def setup(self, context: InjectionContext): """Setup.""" print("Successfully registered DIDIndyRegistry") - async def get_schema(self, profile: Profile, options, schema, issuer_id) -> AnonCredsRegistryGetSchema: + async def get_schema( + self, profile: Profile, options, schema, issuer_id + ) -> AnonCredsRegistryGetSchema: """Get a schema from the registry.""" schema_id = schema.schema_id multitenant_mgr = profile.inject_or(BaseMultitenantManager) @@ -60,7 +68,7 @@ async def get_schema(self, profile: Profile, options, schema, issuer_id) -> Anon reason = "No ledger available" if not profile.settings.get_value("wallet.type"): reason += ": missing wallet-type?" - raise # TODO: create AnonCreds error + raise # TODO: create AnonCreds error async with ledger: try: @@ -68,8 +76,9 @@ async def get_schema(self, profile: Profile, options, schema, issuer_id) -> Anon # TODO: use schema to create AnonCredsSchema and AnonCredsRegistryGetSchema objects # ledger_id goes in resolution_metadata except LedgerError as err: - raise # TODO: create AnonCreds error + raise # TODO: create AnonCreds error return schema + # TODO: job_id? async def get_schemas(self, profile: Profile, filter: str): @@ -81,7 +90,7 @@ async def register_schema( profile: Profile, options: dict, schema, - ): + ): """Register a schema on the registry.""" # TODO: need issuer_id to # issuer_id needs to sign the transaction too @@ -95,7 +104,7 @@ async def register_schema( tag_query=tag_query, ) if 0 < len(found): - raise # Anoncreds error: f"Schema {schema_name} {schema_version} already exists" + raise # Anoncreds error: f"Schema {schema_name} {schema_version} already exists" # Assume endorser role on the network # No option for 3rd-party endorser @@ -105,7 +114,7 @@ async def register_schema( reason = "No ledger available" if not profile.settings.get_value("wallet.type"): reason += ": missing wallet-type?" - raise # Anoncreds error | web.HTTPForbidden(reason=reason) + raise # Anoncreds error | web.HTTPForbidden(reason=reason) issuer = profile.inject(AnonCredsIssuer) async with ledger: @@ -123,8 +132,8 @@ async def register_schema( ) ) except (AnonCredsIssuerError, LedgerError) as err: - raise # Anoncreds error | web.HTTPBadRequest(reason=err.roll_up) from err - + raise # Anoncreds error | web.HTTPBadRequest(reason=err.roll_up) from err + # TODO: use AnonCredsSchema object? meta_data = { "context": { @@ -145,17 +154,15 @@ async def register_schema( "state": "finished", "schema_id": schema_id, "schema": { - "attrNames": schema_def["attrNames"], - "name": schema_def["name"], - "version": schema_def["ver"], - "issuerId": schema.issuer_id - } + "attrNames": schema_def["attrNames"], + "name": schema_def["name"], + "version": schema_def["ver"], + "issuerId": schema.issuer_id, + }, }, "registration_metadata": {}, # For indy, schema_metadata will contain the seqNo - "schema_metadata": { - "seqNo": schema_def["seqNo"] - } + "schema_metadata": {"seqNo": schema_def["seqNo"]}, } async def get_credential_definition( @@ -177,13 +184,13 @@ async def get_credential_definition( reason = "No ledger available" if not profile.settings.get_value("wallet.type"): reason += ": missing wallet-type?" - raise # Anoncreds error | web.HTTPForbidden(reason=reason) + raise # Anoncreds error | web.HTTPForbidden(reason=reason) async with ledger: cred_def = await ledger.get_credential_definition(cred_def_id) return cred_def - + # job_id async def get_credential_definitions(self, profile: Profile, filter: str): @@ -214,7 +221,7 @@ async def register_credential_definition( cred_def_id = record.value cred_def_id_parts = cred_def_id.split(":") if tag == cred_def_id_parts[4]: - raise # Anoncreds error: web.HTTPBadRequest( + raise # Anoncreds error: web.HTTPBadRequest( # reason=f"Cred def for {schema_id} {tag} already exists" # ) @@ -223,7 +230,7 @@ async def register_credential_definition( reason = "No ledger available" if not profile.settings.get_value("wallet.type"): reason += ": missing wallet-type?" - raise # Anoncreds error web.HTTPForbidden(reason=reason) + raise # Anoncreds error web.HTTPForbidden(reason=reason) issuer = profile.inject(AnonCredsIssuer) try: # even if in wallet, send it and raise if erroneously so @@ -235,13 +242,13 @@ async def register_credential_definition( signature_type=None, tag=tag, support_revocation=support_revocation, - write_ledger=True, # TODO: check + write_ledger=True, # TODO: check endorser_did=issuer_id, ) ) except (AnonCredsIssuerError, LedgerError) as e: - raise # Anoncreds error web.HTTPBadRequest(reason=e.message) from e + raise # Anoncreds error web.HTTPBadRequest(reason=e.message) from e issuer_did = cred_def_id.split(":")[0] meta_data = { @@ -263,13 +270,10 @@ async def register_credential_definition( meta_data["processing"]["auto_create_rev_reg"] = True await notify_cred_def_event(profile, cred_def_id, meta_data) - return ( # TODO: check - { - "sent": {"credential_definition_id": cred_def_id}, - "credential_definition_id": cred_def_id, - } - ) - + return { # TODO: check + "sent": {"credential_definition_id": cred_def_id}, + "credential_definition_id": cred_def_id, + } async def get_revocation_registry_definition( self, profile: Profile, rev_reg_id: str @@ -280,7 +284,7 @@ async def get_revocation_registry_definition( revoc = IndyRevocation(profile) rev_reg = await revoc.get_issuer_rev_reg_record(rev_reg_id) except StorageNotFoundError as err: - raise # Anoncreds error web.HTTPNotFound(reason=err.roll_up) from err + raise # Anoncreds error web.HTTPNotFound(reason=err.roll_up) from err return rev_reg.serialize # use AnonCredsRevocationRegistryDefinition object @@ -305,9 +309,9 @@ async def register_revocation_registry_definition( ) LOGGER.debug("published rev reg definition: %s", rev_reg_id) except StorageNotFoundError as err: - raise # Anoncreds error web.HTTPNotFound(reason=err.roll_up) from err + raise # Anoncreds error web.HTTPNotFound(reason=err.roll_up) from err except RevocationError as err: - raise # Anoncreds error web.HTTPBadRequest(reason=err.roll_up) from err + raise # Anoncreds error web.HTTPBadRequest(reason=err.roll_up) from err async def get_revocation_registry_definitions(self, profile: Profile, filter: str): """Get credential definition ids filtered by filter""" diff --git a/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py index c6021a770d..c963ba664e 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py @@ -7,7 +7,8 @@ from ....models.anoncreds_cred_def import ( AnonCredsRegistryGetCredentialDefinition, AnonCredsRegistryGetRevocationList, - AnonCredsRegistryGetRevocationRegistryDefinition) + AnonCredsRegistryGetRevocationRegistryDefinition, +) from ....models.anoncreds_schema import AnonCredsRegistryGetSchema from ...base_registry import BaseAnonCredsRegistrar, BaseAnonCredsResolver diff --git a/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py index 354cd37b2c..3b7b9b948d 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py @@ -7,7 +7,8 @@ from ....models.anoncreds_cred_def import ( AnonCredsRegistryGetCredentialDefinition, AnonCredsRegistryGetRevocationList, - AnonCredsRegistryGetRevocationRegistryDefinition) + AnonCredsRegistryGetRevocationRegistryDefinition, +) from ....models.anoncreds_schema import AnonCredsRegistryGetSchema from ...base_registry import BaseAnonCredsRegistrar, BaseAnonCredsResolver diff --git a/aries_cloudagent/anoncreds/models/anoncreds_cred_def.py b/aries_cloudagent/anoncreds/models/anoncreds_cred_def.py index 68e9b42ba2..6e02d773e1 100644 --- a/aries_cloudagent/anoncreds/models/anoncreds_cred_def.py +++ b/aries_cloudagent/anoncreds/models/anoncreds_cred_def.py @@ -318,6 +318,7 @@ def __init__(self, revocation_definition_ids: list, **kwargs): super().__init__(**kwargs) self.revocation_definition_ids = revocation_definition_ids + class AnonCredsRegistryGetRevocationRegistryDefinitionsSchema(BaseModelSchema): """AnonCredsRegistryGetRevocationRegistryDefinitionsSchema""" @@ -335,6 +336,7 @@ class Meta: ) ) + class AnonCredsRevocationList(BaseModel): """AnonCredsRevocationList""" diff --git a/aries_cloudagent/anoncreds/models/anoncreds_schema.py b/aries_cloudagent/anoncreds/models/anoncreds_schema.py index c995411252..dc030fb264 100644 --- a/aries_cloudagent/anoncreds/models/anoncreds_schema.py +++ b/aries_cloudagent/anoncreds/models/anoncreds_schema.py @@ -109,22 +109,23 @@ class SchemaState(OpenAPISchema): ) schema_ = fields.Nested(AnonCredsSchemaSchema(), data_key="schema") + class AnonCredsRegistryGetSchemas(BaseModel): """SchemasResponAnonCredsRegistryGetSchemasseSchema""" + class Meta: """IndyCredInfo metadata.""" schema_class = "AnonCredsRegistryGetSchemasSchema" - def __init__( - self, - schema_ids: list, - **kwargs - ): + def __init__(self, schema_ids: list, **kwargs): super().__init__(**kwargs) self.schema_ids = schema_ids + + class AnonCredsRegistryGetSchemasSchema(BaseModelSchema): """Parameters and validators for schema list all response.""" + class Meta: """AnonCredsRegistryGetSchemasSchema metadata.""" From e8cd4048a5ab21f93ccd1f54953d62694eeefe77 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Wed, 22 Mar 2023 14:54:13 -0400 Subject: [PATCH 060/150] feat: unskip buggy tests Signed-off-by: Daniel Bluhm --- .../anoncreds/anoncreds/tests/test_cred_issuance.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/aries_cloudagent/anoncreds/anoncreds/tests/test_cred_issuance.py b/aries_cloudagent/anoncreds/anoncreds/tests/test_cred_issuance.py index 3082d048c9..fa2fded348 100644 --- a/aries_cloudagent/anoncreds/anoncreds/tests/test_cred_issuance.py +++ b/aries_cloudagent/anoncreds/anoncreds/tests/test_cred_issuance.py @@ -92,7 +92,6 @@ async def setUp(self): assert "IndyCredxIssuer" in str(self.issuer) assert "IndyCredxVerifier" in str(self.verifier) - @pytest.mark.skip(reason="skipped due to anoncreds-rs issue pending resolution") async def test_issue_store_non_rev(self): assert ( self.issuer.make_schema_id(TEST_DID, SCHEMA_NAME, SCHEMA_VERSION) @@ -194,7 +193,6 @@ async def test_issue_store_non_rev(self): await self.holder.delete_credential(cred_id) - @pytest.mark.skip(reason="skipped due to anoncreds-rs issue pending resolution") async def test_issue_store_rev(self): assert ( self.issuer.make_schema_id(TEST_DID, SCHEMA_NAME, SCHEMA_VERSION) From 0763fec2e385cc4ec45dee657d0507e8067a9fc0 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Fri, 17 Mar 2023 18:07:52 -0600 Subject: [PATCH 061/150] fix: update get_schema signature Signed-off-by: Char Howland --- aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py | 7 ++++--- .../anoncreds/default/did_indy_registry/registry.py | 3 +-- aries_cloudagent/anoncreds/anoncreds/routes.py | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py b/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py index 594a25b57e..542147fea6 100644 --- a/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py @@ -4,6 +4,7 @@ from typing import List, Optional from ...config.injection_context import InjectionContext +from ...core.profile import Profile from ..models.anoncreds_cred_def import ( AnonCredsRegistryGetCredentialDefinition, AnonCredsRegistryGetRevocationList, @@ -52,11 +53,11 @@ async def _registrars_for_identifiers(self, identifier: str): async def setup(self, context: InjectionContext): """Setup method.""" - async def get_schema(self, schema_id: str) -> AnonCredsRegistryGetSchema: + async def get_schema(self, profile: Profile, schema_id: str) -> AnonCredsRegistryGetSchema: """Get a schema from the registry.""" for resolver in await self._resolvers_for_identifier(schema_id): try: - return await resolver.get_schema(schema_id) + return await resolver.get_schema(profile, schema_id) except BaseAnonCredsError: LOGGER.exception("Error getting schema from resolver") @@ -72,7 +73,7 @@ async def get_schemas(self,filter : dict): # TODO: determine keyword arguments - async def register_schema(self, profile, options, schema): + async def register_schema(self, profile: Profile, options, schema): """Register a schema on the registry.""" for registrar in await self._registrars_for_identifiers(schema.issuer_id): try: diff --git a/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py index e9845bfbc7..fd8d990d27 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py @@ -44,9 +44,8 @@ async def setup(self, context: InjectionContext): """Setup.""" print("Successfully registered DIDIndyRegistry") - async def get_schema(self, profile: Profile, options, schema, issuer_id) -> AnonCredsRegistryGetSchema: + async def get_schema(self, profile: Profile, schema_id) -> AnonCredsRegistryGetSchema: """Get a schema from the registry.""" - schema_id = schema.schema_id multitenant_mgr = profile.inject_or(BaseMultitenantManager) if multitenant_mgr: ledger_exec_inst = IndyLedgerRequestsExecutor(profile) diff --git a/aries_cloudagent/anoncreds/anoncreds/routes.py b/aries_cloudagent/anoncreds/anoncreds/routes.py index ce26191b76..81e8381309 100644 --- a/aries_cloudagent/anoncreds/anoncreds/routes.py +++ b/aries_cloudagent/anoncreds/anoncreds/routes.py @@ -218,7 +218,7 @@ async def schema_get(request: web.BaseRequest): context: AdminRequestContext = request["context"] anon_creds_registry = context.inject(AnonCredsRegistry) schema_id = request.match_info["schemaId"] - result = await anon_creds_registry.get_schema(schema_id) + result = await anon_creds_registry.get_schema(context.profile, schema_id) return web.json_response(result) From 0ee07691f53bb34c47c270ecf7c068242145aebb Mon Sep 17 00:00:00 2001 From: Char Howland Date: Tue, 21 Mar 2023 01:52:12 -0600 Subject: [PATCH 062/150] fix: credential_definition method signatures Signed-off-by: Char Howland --- .../anoncreds/anoncreds/anoncreds_registry.py | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py b/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py index 542147fea6..3aad6cbb4c 100644 --- a/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py @@ -84,13 +84,14 @@ async def register_schema(self, profile: Profile, options, schema): raise AnonCredsRegistrationFailed("Failed to register schema") async def get_credential_definition( - self, credential_definition_id: str + self, profile: Profile, credential_definition_id: str ) -> AnonCredsRegistryGetCredentialDefinition: """Get a credential definition from the registry.""" for resolver in await self._resolvers_for_identifier(credential_definition_id): try: return await resolver.get_credential_definition( - credential_definition_id + profile, + credential_definition_id, ) except BaseAnonCredsError: LOGGER.exception("Error getting credential definition from resolver") @@ -110,11 +111,26 @@ async def get_credential_definitions( return itertools.chain.from_iterable(results) # TODO: determine keyword arguments - async def register_credential_definition(self): + async def register_credential_definition( + self, + profile: Profile, + schema_id: str, + support_revocation: bool, + tag: str, + rev_reg_size: int, + issuer_id: str, + ): """Register a credential definition on the registry.""" for registrar in await self._registrars_for_identifiers("something"): try: - return await registrar.register_credential_definition() + return await registrar.register_credential_definition( + profile, + schema_id, + support_revocation, + tag, + rev_reg_size, + issuer_id, + ) except BaseAnonCredsError: LOGGER.exception("Error registering schema with registrar") From e415799c10d81ed909f1725849ac06b741842f38 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Tue, 21 Mar 2023 01:52:33 -0600 Subject: [PATCH 063/150] fix: did:web method signatures Signed-off-by: Char Howland --- .../anoncreds/default/did_web_registry/registry.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py index c6021a770d..76aedade17 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py @@ -27,7 +27,7 @@ async def setup(self, context: InjectionContext): """Setup.""" print("Successfully registered DIDWebRegistry") - async def get_schema(self, schema_id: str) -> AnonCredsRegistryGetSchema: + async def get_schema(self, profile, schema_id: str) -> AnonCredsRegistryGetSchema: """Get a schema from the registry.""" async def get_schemas(self, profile: Profile, filter: str): @@ -43,7 +43,7 @@ async def register_schema( """Register a schema on the registry.""" async def get_credential_definition( - self, credential_definition_id: str + self, profile, credential_definition_id: str ) -> AnonCredsRegistryGetCredentialDefinition: """Get a credential definition from the registry.""" @@ -51,7 +51,15 @@ async def get_credential_definitions(self, profile: Profile, filter: str): """Get credential definition ids filtered by filter""" # TODO: determine keyword arguments - async def register_credential_definition(self): + async def register_credential_definition( + self, + profile: Profile, + schema_id: str, + support_revocation: bool, + tag: str, + rev_reg_size: int, + issuer_id: str, + ): """Register a credential definition on the registry.""" async def get_revocation_registry_definition( From 6d8866786337171866e94903cd221a3b8dbf6467 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Tue, 21 Mar 2023 01:53:01 -0600 Subject: [PATCH 064/150] feat: legacy indy schema, cred def logic Signed-off-by: Char Howland --- .../default/legacy_indy_registry/registry.py | 286 +++++++++++++++++- 1 file changed, 278 insertions(+), 8 deletions(-) diff --git a/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py index 354cd37b2c..aa79977b36 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py @@ -1,39 +1,93 @@ """Legacy Indy Registry""" +import logging import re +from asyncio import shield from typing import Pattern from .....config.injection_context import InjectionContext from .....core.profile import Profile +from .....ledger.base import BaseLedger +from .....ledger.error import LedgerError +from .....ledger.merkel_validation.constants import GET_SCHEMA +from .....ledger.multiple_ledger.ledger_requests_executor import ( + GET_CRED_DEF, IndyLedgerRequestsExecutor) +from .....messaging.credential_definitions.util import ( + CRED_DEF_SENT_RECORD_TYPE, notify_cred_def_event) +from .....messaging.schemas.util import (SCHEMA_SENT_RECORD_TYPE, + notify_schema_event) +from .....multitenant.base import BaseMultitenantManager +from .....revocation.error import RevocationError +from .....revocation.indy import IndyRevocation +from .....storage.base import BaseStorage +from .....storage.error import StorageNotFoundError +from ....issuer import AnonCredsIssuer, AnonCredsIssuerError from ....models.anoncreds_cred_def import ( + AnonCredsCredentialDefinition, + AnonCredsCredentialDefinitionValue, AnonCredsRegistryGetCredentialDefinition, AnonCredsRegistryGetRevocationList, AnonCredsRegistryGetRevocationRegistryDefinition) -from ....models.anoncreds_schema import AnonCredsRegistryGetSchema +from ....models.anoncreds_schema import AnonCredsRegistryGetSchema, AnonCredsSchema from ...base_registry import BaseAnonCredsRegistrar, BaseAnonCredsResolver +LOGGER = logging.getLogger(__name__) + class LegacyIndyRegistry(BaseAnonCredsResolver, BaseAnonCredsRegistrar): """LegacyIndyRegistry""" def __init__(self): self._supported_identifiers_regex = re.compile(r"^(?!did).*$") + # TODO: fix regex (too general) @property def supported_identifiers_regex(self) -> Pattern: return self._supported_identifiers_regex - # TODO: fix regex (too general) async def setup(self, context: InjectionContext): """Setup.""" print("Successfully registered LegacyIndyRegistry") - async def get_schema(self, schema_id: str) -> AnonCredsRegistryGetSchema: + async def get_schema(self, profile: Profile, schema_id: str) -> AnonCredsRegistryGetSchema: """Get a schema from the registry.""" + multitenant_mgr = profile.inject_or(BaseMultitenantManager) + if multitenant_mgr: + ledger_exec_inst = IndyLedgerRequestsExecutor(profile) + else: + ledger_exec_inst = profile.inject(IndyLedgerRequestsExecutor) + ledger_id, ledger = await ledger_exec_inst.get_ledger_for_identifier( + schema_id, + txn_record_type=GET_SCHEMA, + ) + if not ledger: + reason = "No ledger available" + if not profile.settings.get_value("wallet.type"): + reason += ": missing wallet-type?" + raise # TODO: AnonCreds error + + async with ledger: + try: + schema = await ledger.get_schema(schema_id) + anonscreds_schema = AnonCredsSchema( + issuer_id=schema["id"].split(":")[0], + attr_names=schema["attrNames"], + name=schema["name"], + version=schema["ver"] + ) + anoncreds_registry_get_schema = AnonCredsRegistryGetSchema( + schema=anonscreds_schema, + schema_id=schema["id"], + resolution_metadata={"ledger_id": ledger_id}, + schema_metadata={"seqNo": schema["seqNo"]} + ) + except LedgerError as err: + raise # TODO: AnonCreds error + return anoncreds_registry_get_schema + async def get_schemas(self, profile: Profile, filter: str): """Get schema ids filtered by filter""" - # TODO: determine keyword arguments async def register_schema( self, profile: Profile, @@ -41,31 +95,247 @@ async def register_schema( schema, ): """Register a schema on the registry.""" + # Check that schema doesn't already exist + tag_query = {"schema_name": schema.name, "schema_version": schema.version} + async with profile.session() as session: + storage = session.inject(BaseStorage) + found = await storage.find_all_records( + type_filter=SCHEMA_SENT_RECORD_TYPE, + tag_query=tag_query, + ) + if 0 < len(found): + raise # TODO: Anoncreds error: f"Schema {schema_name} {schema_version} already exists" + + # Assume endorser role on the network, no option for 3rd-party endorser + ledger = profile.inject_or(BaseLedger) + if not ledger: + reason = "No ledger available" + if not profile.settings.get_value("wallet.type"): + reason += ": missing wallet-type?" + raise # TODO: Anoncreds error + + issuer = profile.inject(AnonCredsIssuer) + async with ledger: + try: + schema_id, schema_def = await shield( + ledger.create_and_send_schema( + issuer, + schema.name, + schema.version, + schema.attr_names, + write_ledger=True, # TODO: check + endorser_did=schema.issuer_id, + ) + ) + except (AnonCredsIssuerError, LedgerError) as err: + raise # TODO: Anoncreds error + + # TODO: use AnonCredsSchema object? + meta_data = { + "context": { + "schema_id": schema_id, + "schema_name": schema.name, + "schema_version": schema.version, + "attributes": schema.attr_names, + }, + "processing": {}, + } + + # Notify event + await notify_schema_event(profile, schema_id, meta_data) + + return { + "job_id": None, + "schema_state": { + "state": "finished", + "schema_id": schema_id, + "schema": { + "attrNames": schema_def["attrNames"], + "name": schema_def["name"], + "version": schema_def["ver"], + "issuerId": schema.issuer_id + } + }, + "registration_metadata": {}, + "schema_metadata": { + "seqNo": schema_def["seqNo"] + } + } async def get_credential_definition( - self, credential_definition_id: str + self, profile: Profile, cred_def_id: str ) -> AnonCredsRegistryGetCredentialDefinition: """Get a credential definition from the registry.""" +<<<<<<< HEAD async def get_credential_definitions(self, profile: Profile, filter: str): """Get credential definition ids filtered by filter""" +======= + async with profile.session() as session: + multitenant_mgr = session.inject_or(BaseMultitenantManager) + if multitenant_mgr: + ledger_exec_inst = IndyLedgerRequestsExecutor(profile) + else: + ledger_exec_inst = session.inject(IndyLedgerRequestsExecutor) + ledger_id, ledger = await ledger_exec_inst.get_ledger_for_identifier( + cred_def_id, + txn_record_type=GET_CRED_DEF, + ) + if not ledger: + reason = "No ledger available" + if not profile.settings.get_value("wallet.type"): + reason += ": missing wallet-type?" + raise # Anoncreds error | web.HTTPForbidden(reason=reason) + + async with ledger: + cred_def = await ledger.get_credential_definition(cred_def_id) + anoncreds_credential_definition_value = AnonCredsCredentialDefinitionValue( + primary=cred_def["value"] + ) + anoncreds_credential_definition = AnonCredsCredentialDefinition( + issuer_id=cred_def["id"].split(":")[0], + schema_id=cred_def["schemaId"], + type=cred_def["type"], + tag=cred_def["tag"], + value=anoncreds_credential_definition_value, + ) + anoncreds_registry_get_credential_definition = AnonCredsRegistryGetCredentialDefinition( + credential_definition=anoncreds_credential_definition, + credential_definition_id=cred_def["id"], + resolution_metadata={}, + credential_definition_metadata={}, + ) + return cred_def + + # job_id + +>>>>>>> feat: legacy indy schema, cred def logic # TODO: determine keyword arguments - async def register_credential_definition(self): + async def register_credential_definition( + self, + profile: Profile, + schema_id: str, + support_revocation: bool, + tag: str, + rev_reg_size: int, + issuer_id: str, + ): """Register a credential definition on the registry.""" + ledger = profile.inject_or(BaseLedger) + if not ledger: + reason = "No ledger available" + if not profile.settings.get_value("wallet.type"): + reason += ": missing wallet-type?" + raise # Anoncreds error web.HTTPForbidden(reason=reason) + + issuer = profile.inject(AnonCredsIssuer) + try: # even if in wallet, send it and raise if erroneously so + async with ledger: + (cred_def_id, cred_def, novel) = await shield( + ledger.create_and_send_credential_definition( + issuer, + schema_id, + signature_type=None, + tag=tag, + support_revocation=support_revocation, + write_ledger=True, # TODO: check + endorser_did=issuer_id, + ) + ) + + except (AnonCredsIssuerError, LedgerError) as e: + raise # Anoncreds error web.HTTPBadRequest(reason=e.message) from e + + issuer_did = cred_def_id.split(":")[0] + meta_data = { + "context": { + "schema_id": schema_id, + "cred_def_id": cred_def_id, + "issuer_did": issuer_did, + "support_revocation": support_revocation, + "novel": novel, + "tag": tag, + "rev_reg_size": rev_reg_size, + }, + "processing": { + "create_pending_rev_reg": True, + }, + } + + # Notify event + meta_data["processing"]["auto_create_rev_reg"] = True + await notify_cred_def_event(profile, cred_def_id, meta_data) + + return { + "job_id": None, + "credential_definition_state": { + "state": "finished", + "credential_definition_id": cred_def_id, + "credential_definition": { + "issuerId": issuer_did, + "schemaId": schema_id, + "type": "CL", + "tag": tag, + "value": { + "primary": { + "n": cred_def["value"]["primary"]["n"], + "r": cred_def["value"]["primary"]["r"], + "rctxt": cred_def["value"]["primary"]["rctxt"], + "s": cred_def["value"]["primary"]["s"], + "z": cred_def["value"]["primary"]["z"] + } + } + } + }, + "registration_metadata": {}, + "credential_definition_metadata": {} + } + async def get_revocation_registry_definition( - self, revocation_registry_id: str + self, profile: Profile, rev_reg_id: str ) -> AnonCredsRegistryGetRevocationRegistryDefinition: """Get a revocation registry definition from the registry.""" +<<<<<<< HEAD async def get_revocation_registry_definitions(self, profile: Profile, filter: str): """Get credential definition ids filtered by filter""" +======= + try: + revoc = IndyRevocation(profile) + rev_reg = await revoc.get_issuer_rev_reg_record(rev_reg_id) + except StorageNotFoundError as err: + raise # Anoncreds error web.HTTPNotFound(reason=err.roll_up) from err + + return rev_reg.serialize + # use AnonCredsRevocationRegistryDefinition object +>>>>>>> feat: legacy indy schema, cred def logic # TODO: determine keyword arguments - async def register_revocation_registry_definition(self): + async def register_revocation_registry_definition( + self, + profile: Profile, + rev_reg_id: str, + issuer_id: str, + ): """Register a revocation registry definition on the registry.""" + try: + revoc = IndyRevocation(profile) + rev_reg = await revoc.get_issuer_rev_reg_record(rev_reg_id) + + rev_reg_resp = await rev_reg.send_def( + profile, + write_ledger=True, + endorser_did=issuer_id, + ) + LOGGER.debug("published rev reg definition: %s", rev_reg_id) + except StorageNotFoundError as err: + raise # Anoncreds error web.HTTPNotFound(reason=err.roll_up) from err + except RevocationError as err: + raise # Anoncreds error web.HTTPBadRequest(reason=err.roll_up) from err + async def get_revocation_list( self, revocation_registry_id: str, timestamp: str ) -> AnonCredsRegistryGetRevocationList: From f43f669e9a9e426ef4c3ec9b20f04136486ed671 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Tue, 21 Mar 2023 01:54:37 -0600 Subject: [PATCH 065/150] fix: anon_creds_registry credential definition arguments Signed-off-by: Char Howland --- .../anoncreds/anoncreds/routes.py | 44 ++++++++----------- 1 file changed, 19 insertions(+), 25 deletions(-) diff --git a/aries_cloudagent/anoncreds/anoncreds/routes.py b/aries_cloudagent/anoncreds/anoncreds/routes.py index 81e8381309..0f18249b94 100644 --- a/aries_cloudagent/anoncreds/anoncreds/routes.py +++ b/aries_cloudagent/anoncreds/anoncreds/routes.py @@ -2,21 +2,16 @@ import logging from aiohttp import web -from aiohttp_apispec import ( - docs, - match_info_schema, - querystring_schema, - request_schema, - response_schema, -) +from aiohttp_apispec import (docs, match_info_schema, querystring_schema, + request_schema, response_schema) from marshmallow import fields -from aries_cloudagent.anoncreds.anoncreds.anoncreds_registry import AnonCredsRegistry + +from aries_cloudagent.anoncreds.anoncreds.anoncreds_registry import \ + AnonCredsRegistry from aries_cloudagent.anoncreds.models.anoncreds_cred_def import ( - AnonCredsCredentialDefinitionValueSchema, - AnonCredsRegistryGetCredentialDefinitionSchema, AnonCredsCredentialDefinitionSchema, -) - + AnonCredsCredentialDefinitionValueSchema, + AnonCredsRegistryGetCredentialDefinitionSchema) from aries_cloudagent.anoncreds.models.anoncreds_schema import ( AnonCredsRegistryGetSchemasSchema, AnonCredsSchema, @@ -24,22 +19,14 @@ SchemaPostQueryStringSchema, AnonCredsRegistryGetSchemaSchema, SchemasQueryStringSchema, - AnonCredsRegistryGetSchemas, ) from aries_cloudagent.anoncreds.models.anoncreds_valid import ( - ANONCREDS_SCHEMA_ID, - ANONCREDS_VERSION, -) + ANONCREDS_SCHEMA_ID, ANONCREDS_VERSION) from ...admin.request_context import AdminRequestContext from ...messaging.models.openapi import OpenAPISchema -from ...messaging.valid import ( - GENERIC_DID, - INDY_CRED_DEF_ID, - INDY_SCHEMA_ID, - INDY_VERSION, - UUIDFour, -) +from ...messaging.valid import (GENERIC_DID, INDY_CRED_DEF_ID, INDY_SCHEMA_ID, + INDY_VERSION, UUIDFour) LOGGER = logging.getLogger(__name__) @@ -273,7 +260,14 @@ async def cred_def_post(request: web.BaseRequest): "support_revocation": data.get("supportRevocation"), "revocation_registrySize": data.get("revocationRegistrySize"), } - result = anon_creds_registry.register_credential_definition(options, cred_def) + result = await anon_creds_registry.register_credential_definition( + profile=context.profile, + schema_id=data["schemaId"], + support_revocation=data["supportRevocation"], + tag=None, + rev_reg_size=data["revocationRegistrySize"], + issuer_id=data["issuerId"], + ) parameters = await request.json() return web.json_response(result) @@ -292,7 +286,7 @@ async def cred_def_get(request: web.BaseRequest): context: AdminRequestContext = request["context"] anon_creds_registry = context.inject(AnonCredsRegistry) credential_id = request.match_info["cred_def_id"] - result = await anon_creds_registry.get_credential_definition(credential_id) + result = await anon_creds_registry.get_credential_definition(context.profile, credential_id) return web.json_response(result) From 001420596d1d14e1d701fc6115a397a7c29a61b0 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Wed, 22 Mar 2023 09:51:33 -0600 Subject: [PATCH 066/150] fix: serialize schema_get result Signed-off-by: Char Howland --- aries_cloudagent/anoncreds/anoncreds/routes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aries_cloudagent/anoncreds/anoncreds/routes.py b/aries_cloudagent/anoncreds/anoncreds/routes.py index 0f18249b94..7c4b5c04a5 100644 --- a/aries_cloudagent/anoncreds/anoncreds/routes.py +++ b/aries_cloudagent/anoncreds/anoncreds/routes.py @@ -207,7 +207,7 @@ async def schema_get(request: web.BaseRequest): schema_id = request.match_info["schemaId"] result = await anon_creds_registry.get_schema(context.profile, schema_id) - return web.json_response(result) + return web.json_response(result.serialize()) @docs(tags=["anoncreds"], summary="") From 6e41a0681bff11dae25f84f4b34a19f0e0aabc4f Mon Sep 17 00:00:00 2001 From: Char Howland Date: Wed, 22 Mar 2023 10:14:30 -0600 Subject: [PATCH 067/150] fix: merge cleanup Signed-off-by: Char Howland --- .../default/legacy_indy_registry/registry.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py index aa79977b36..fe8986b883 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py @@ -167,10 +167,6 @@ async def get_credential_definition( ) -> AnonCredsRegistryGetCredentialDefinition: """Get a credential definition from the registry.""" -<<<<<<< HEAD - async def get_credential_definitions(self, profile: Profile, filter: str): - """Get credential definition ids filtered by filter""" -======= async with profile.session() as session: multitenant_mgr = session.inject_or(BaseMultitenantManager) if multitenant_mgr: @@ -209,7 +205,8 @@ async def get_credential_definitions(self, profile: Profile, filter: str): # job_id ->>>>>>> feat: legacy indy schema, cred def logic + async def get_credential_definitions(self, profile: Profile, filter: str): + """Get credential definition ids filtered by filter""" # TODO: determine keyword arguments async def register_credential_definition( @@ -298,10 +295,6 @@ async def get_revocation_registry_definition( ) -> AnonCredsRegistryGetRevocationRegistryDefinition: """Get a revocation registry definition from the registry.""" -<<<<<<< HEAD - async def get_revocation_registry_definitions(self, profile: Profile, filter: str): - """Get credential definition ids filtered by filter""" -======= try: revoc = IndyRevocation(profile) rev_reg = await revoc.get_issuer_rev_reg_record(rev_reg_id) @@ -310,7 +303,9 @@ async def get_revocation_registry_definitions(self, profile: Profile, filter: st return rev_reg.serialize # use AnonCredsRevocationRegistryDefinition object ->>>>>>> feat: legacy indy schema, cred def logic + + async def get_revocation_registry_definitions(self, profile: Profile, filter: str): + """Get credential definition ids filtered by filter""" # TODO: determine keyword arguments async def register_revocation_registry_definition( From 52342c0886b5066ee54b04cb6d34334060682aa6 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Wed, 22 Mar 2023 11:20:06 -0600 Subject: [PATCH 068/150] feat: error reporting Signed-off-by: Char Howland --- .../anoncreds/anoncreds/base_registry.py | 18 ++-- .../default/legacy_indy_registry/registry.py | 99 ++++++++++--------- 2 files changed, 64 insertions(+), 53 deletions(-) diff --git a/aries_cloudagent/anoncreds/anoncreds/base_registry.py b/aries_cloudagent/anoncreds/anoncreds/base_registry.py index 1cb512ed43..01c8e5030f 100644 --- a/aries_cloudagent/anoncreds/anoncreds/base_registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/base_registry.py @@ -10,7 +10,10 @@ AnonCredsRegistryGetCredentialDefinitions, AnonCredsRegistryGetRevocationRegistryDefinitions, ) -from ..models.anoncreds_schema import AnonCredsRegistryGetSchema, AnonCredsRegistryGetSchemas +from ..models.anoncreds_schema import ( + AnonCredsRegistryGetSchema, + AnonCredsRegistryGetSchemas, +) class BaseAnonCredsError(Exception): @@ -25,6 +28,10 @@ class AnonCredsRegistrationFailed(BaseAnonCredsError): """Raised when registering an AnonCreds object fails.""" +class AnonCredsResolutionFailed(BaseAnonCredsError): + """Raised when resolving an AnonCreds object fails.""" + + class BaseAnonCredsHandler(ABC): @property @abstractmethod @@ -44,7 +51,7 @@ class BaseAnonCredsResolver(BaseAnonCredsHandler): @abstractmethod async def get_schema(self, schema_id: str) -> AnonCredsRegistryGetSchema: """Get a schema from the registry.""" - + @abstractmethod async def get_schemas(self, filter: dict) -> AnonCredsRegistryGetSchemas: """Get a schema ids from the registry.""" @@ -54,7 +61,7 @@ async def get_credential_definition( self, credential_definition_id: str ) -> AnonCredsRegistryGetCredentialDefinition: """Get a credential definition from the registry.""" - + @abstractmethod async def get_credential_definitions( self, filter: dict @@ -66,10 +73,10 @@ async def get_revocation_registry_definition( self, revocation_registry_id: str ) -> AnonCredsRegistryGetRevocationRegistryDefinition: """Get a revocation registry definition from the registry.""" - + @abstractmethod async def get_revocation_registry_definitions( - self, filter: dict + self, filter: dict ) -> AnonCredsRegistryGetRevocationRegistryDefinitions: """Get a revocation registry definition ids from the registry.""" @@ -80,7 +87,6 @@ async def get_revocation_list( """Get a revocation list from the registry.""" - class BaseAnonCredsRegistrar(BaseAnonCredsHandler): # TODO: determine keyword arguments diff --git a/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py index fe8986b883..4dd01f1b1e 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py @@ -10,11 +10,11 @@ from .....ledger.error import LedgerError from .....ledger.merkel_validation.constants import GET_SCHEMA from .....ledger.multiple_ledger.ledger_requests_executor import ( - GET_CRED_DEF, IndyLedgerRequestsExecutor) -from .....messaging.credential_definitions.util import ( - CRED_DEF_SENT_RECORD_TYPE, notify_cred_def_event) -from .....messaging.schemas.util import (SCHEMA_SENT_RECORD_TYPE, - notify_schema_event) + GET_CRED_DEF, + IndyLedgerRequestsExecutor, +) +from .....messaging.credential_definitions.util import notify_cred_def_event +from .....messaging.schemas.util import SCHEMA_SENT_RECORD_TYPE, notify_schema_event from .....multitenant.base import BaseMultitenantManager from .....revocation.error import RevocationError from .....revocation.indy import IndyRevocation @@ -26,9 +26,15 @@ AnonCredsCredentialDefinitionValue, AnonCredsRegistryGetCredentialDefinition, AnonCredsRegistryGetRevocationList, - AnonCredsRegistryGetRevocationRegistryDefinition) + AnonCredsRegistryGetRevocationRegistryDefinition, +) from ....models.anoncreds_schema import AnonCredsRegistryGetSchema, AnonCredsSchema -from ...base_registry import BaseAnonCredsRegistrar, BaseAnonCredsResolver +from ...base_registry import ( + AnonCredsRegistrationFailed, + AnonCredsResolutionFailed, + BaseAnonCredsRegistrar, + BaseAnonCredsResolver, +) LOGGER = logging.getLogger(__name__) @@ -48,7 +54,9 @@ async def setup(self, context: InjectionContext): """Setup.""" print("Successfully registered LegacyIndyRegistry") - async def get_schema(self, profile: Profile, schema_id: str) -> AnonCredsRegistryGetSchema: + async def get_schema( + self, profile: Profile, schema_id: str + ) -> AnonCredsRegistryGetSchema: """Get a schema from the registry.""" multitenant_mgr = profile.inject_or(BaseMultitenantManager) @@ -64,7 +72,7 @@ async def get_schema(self, profile: Profile, schema_id: str) -> AnonCredsRegistr reason = "No ledger available" if not profile.settings.get_value("wallet.type"): reason += ": missing wallet-type?" - raise # TODO: AnonCreds error + raise AnonCredsResolutionFailed(reason) async with ledger: try: @@ -73,16 +81,16 @@ async def get_schema(self, profile: Profile, schema_id: str) -> AnonCredsRegistr issuer_id=schema["id"].split(":")[0], attr_names=schema["attrNames"], name=schema["name"], - version=schema["ver"] + version=schema["ver"], ) anoncreds_registry_get_schema = AnonCredsRegistryGetSchema( schema=anonscreds_schema, schema_id=schema["id"], resolution_metadata={"ledger_id": ledger_id}, - schema_metadata={"seqNo": schema["seqNo"]} + schema_metadata={"seqNo": schema["seqNo"]}, ) except LedgerError as err: - raise # TODO: AnonCreds error + raise AnonCredsResolutionFailed(err) return anoncreds_registry_get_schema async def get_schemas(self, profile: Profile, filter: str): @@ -104,7 +112,9 @@ async def register_schema( tag_query=tag_query, ) if 0 < len(found): - raise # TODO: Anoncreds error: f"Schema {schema_name} {schema_version} already exists" + raise AnonCredsRegistrationFailed( + f"Schema {schema.name} {schema.version} already exists." + ) # Assume endorser role on the network, no option for 3rd-party endorser ledger = profile.inject_or(BaseLedger) @@ -112,7 +122,7 @@ async def register_schema( reason = "No ledger available" if not profile.settings.get_value("wallet.type"): reason += ": missing wallet-type?" - raise # TODO: Anoncreds error + raise AnonCredsRegistrationFailed(reason) issuer = profile.inject(AnonCredsIssuer) async with ledger: @@ -128,9 +138,8 @@ async def register_schema( ) ) except (AnonCredsIssuerError, LedgerError) as err: - raise # TODO: Anoncreds error - - # TODO: use AnonCredsSchema object? + raise AnonCredsRegistrationFailed(err) + meta_data = { "context": { "schema_id": schema_id, @@ -140,7 +149,6 @@ async def register_schema( }, "processing": {}, } - # Notify event await notify_schema_event(profile, schema_id, meta_data) @@ -150,16 +158,14 @@ async def register_schema( "state": "finished", "schema_id": schema_id, "schema": { - "attrNames": schema_def["attrNames"], - "name": schema_def["name"], - "version": schema_def["ver"], - "issuerId": schema.issuer_id - } + "attrNames": schema_def["attrNames"], + "name": schema_def["name"], + "version": schema_def["ver"], + "issuerId": schema.issuer_id, + }, }, "registration_metadata": {}, - "schema_metadata": { - "seqNo": schema_def["seqNo"] - } + "schema_metadata": {"seqNo": schema_def["seqNo"]}, } async def get_credential_definition( @@ -181,7 +187,7 @@ async def get_credential_definition( reason = "No ledger available" if not profile.settings.get_value("wallet.type"): reason += ": missing wallet-type?" - raise # Anoncreds error | web.HTTPForbidden(reason=reason) + raise AnonCredsResolutionFailed(reason) async with ledger: cred_def = await ledger.get_credential_definition(cred_def_id) @@ -195,20 +201,19 @@ async def get_credential_definition( tag=cred_def["tag"], value=anoncreds_credential_definition_value, ) - anoncreds_registry_get_credential_definition = AnonCredsRegistryGetCredentialDefinition( - credential_definition=anoncreds_credential_definition, - credential_definition_id=cred_def["id"], - resolution_metadata={}, - credential_definition_metadata={}, + anoncreds_registry_get_credential_definition = ( + AnonCredsRegistryGetCredentialDefinition( + credential_definition=anoncreds_credential_definition, + credential_definition_id=cred_def["id"], + resolution_metadata={}, + credential_definition_metadata={}, + ) ) - return cred_def - - # job_id + return anoncreds_registry_get_credential_definition async def get_credential_definitions(self, profile: Profile, filter: str): """Get credential definition ids filtered by filter""" - # TODO: determine keyword arguments async def register_credential_definition( self, profile: Profile, @@ -225,7 +230,7 @@ async def register_credential_definition( reason = "No ledger available" if not profile.settings.get_value("wallet.type"): reason += ": missing wallet-type?" - raise # Anoncreds error web.HTTPForbidden(reason=reason) + raise AnonCredsRegistrationFailed(reason) issuer = profile.inject(AnonCredsIssuer) try: # even if in wallet, send it and raise if erroneously so @@ -237,13 +242,13 @@ async def register_credential_definition( signature_type=None, tag=tag, support_revocation=support_revocation, - write_ledger=True, # TODO: check + write_ledger=True, endorser_did=issuer_id, ) ) except (AnonCredsIssuerError, LedgerError) as e: - raise # Anoncreds error web.HTTPBadRequest(reason=e.message) from e + raise AnonCredsRegistrationFailed(e) issuer_did = cred_def_id.split(":")[0] meta_data = { @@ -281,13 +286,13 @@ async def register_credential_definition( "r": cred_def["value"]["primary"]["r"], "rctxt": cred_def["value"]["primary"]["rctxt"], "s": cred_def["value"]["primary"]["s"], - "z": cred_def["value"]["primary"]["z"] + "z": cred_def["value"]["primary"]["z"], } - } - } + }, + }, }, "registration_metadata": {}, - "credential_definition_metadata": {} + "credential_definition_metadata": {}, } async def get_revocation_registry_definition( @@ -299,7 +304,7 @@ async def get_revocation_registry_definition( revoc = IndyRevocation(profile) rev_reg = await revoc.get_issuer_rev_reg_record(rev_reg_id) except StorageNotFoundError as err: - raise # Anoncreds error web.HTTPNotFound(reason=err.roll_up) from err + raise AnonCredsResolutionFailed(err) return rev_reg.serialize # use AnonCredsRevocationRegistryDefinition object @@ -320,16 +325,16 @@ async def register_revocation_registry_definition( revoc = IndyRevocation(profile) rev_reg = await revoc.get_issuer_rev_reg_record(rev_reg_id) - rev_reg_resp = await rev_reg.send_def( + await rev_reg.send_def( profile, write_ledger=True, endorser_did=issuer_id, ) LOGGER.debug("published rev reg definition: %s", rev_reg_id) except StorageNotFoundError as err: - raise # Anoncreds error web.HTTPNotFound(reason=err.roll_up) from err + raise AnonCredsRegistrationFailed(err) except RevocationError as err: - raise # Anoncreds error web.HTTPBadRequest(reason=err.roll_up) from err + raise AnonCredsRegistrationFailed(err) async def get_revocation_list( self, revocation_registry_id: str, timestamp: str From 2039b6679bb772c97982c8b56ddb0c7edb1d45ca Mon Sep 17 00:00:00 2001 From: Char Howland Date: Wed, 22 Mar 2023 11:29:22 -0600 Subject: [PATCH 069/150] fix: serialize cred_def_get result Signed-off-by: Char Howland --- .../default/legacy_indy_registry/registry.py | 1 + .../anoncreds/anoncreds/routes.py | 50 ++++++++++++------- 2 files changed, 32 insertions(+), 19 deletions(-) diff --git a/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py index 4dd01f1b1e..db1b0f2614 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py @@ -222,6 +222,7 @@ async def register_credential_definition( tag: str, rev_reg_size: int, issuer_id: str, + options, # TODO: handle options ): """Register a credential definition on the registry.""" diff --git a/aries_cloudagent/anoncreds/anoncreds/routes.py b/aries_cloudagent/anoncreds/anoncreds/routes.py index 7c4b5c04a5..98c5400d13 100644 --- a/aries_cloudagent/anoncreds/anoncreds/routes.py +++ b/aries_cloudagent/anoncreds/anoncreds/routes.py @@ -2,16 +2,20 @@ import logging from aiohttp import web -from aiohttp_apispec import (docs, match_info_schema, querystring_schema, - request_schema, response_schema) +from aiohttp_apispec import ( + docs, + match_info_schema, + querystring_schema, + request_schema, + response_schema, +) from marshmallow import fields -from aries_cloudagent.anoncreds.anoncreds.anoncreds_registry import \ - AnonCredsRegistry +from aries_cloudagent.anoncreds.anoncreds.anoncreds_registry import AnonCredsRegistry from aries_cloudagent.anoncreds.models.anoncreds_cred_def import ( AnonCredsCredentialDefinitionSchema, - AnonCredsCredentialDefinitionValueSchema, - AnonCredsRegistryGetCredentialDefinitionSchema) + AnonCredsRegistryGetCredentialDefinitionSchema, +) from aries_cloudagent.anoncreds.models.anoncreds_schema import ( AnonCredsRegistryGetSchemasSchema, AnonCredsSchema, @@ -21,12 +25,18 @@ SchemasQueryStringSchema, ) from aries_cloudagent.anoncreds.models.anoncreds_valid import ( - ANONCREDS_SCHEMA_ID, ANONCREDS_VERSION) + ANONCREDS_SCHEMA_ID, + ANONCREDS_VERSION, +) from ...admin.request_context import AdminRequestContext from ...messaging.models.openapi import OpenAPISchema -from ...messaging.valid import (GENERIC_DID, INDY_CRED_DEF_ID, INDY_SCHEMA_ID, - INDY_VERSION, UUIDFour) +from ...messaging.valid import ( + GENERIC_DID, + INDY_CRED_DEF_ID, + INDY_SCHEMA_ID, + UUIDFour, +) LOGGER = logging.getLogger(__name__) @@ -253,13 +263,13 @@ async def cred_def_post(request: web.BaseRequest): data = request_data.get("credentialDefinition") # TODO: find out if we need a model for this input. - cred_def = { - "issuer_id": data.get("issuerId"), - "schema_id": data.get("schemaId"), - # "tag":data.get("tag"), - "support_revocation": data.get("supportRevocation"), - "revocation_registrySize": data.get("revocationRegistrySize"), - } + # cred_def = { + # "issuer_id": data.get("issuerId"), + # "schema_id": data.get("schemaId"), + # # "tag":data.get("tag"), + # "support_revocation": data.get("supportRevocation"), + # "revocation_registrySize": data.get("revocationRegistrySize"), + # } result = await anon_creds_registry.register_credential_definition( profile=context.profile, schema_id=data["schemaId"], @@ -267,8 +277,8 @@ async def cred_def_post(request: web.BaseRequest): tag=None, rev_reg_size=data["revocationRegistrySize"], issuer_id=data["issuerId"], + options=options, ) - parameters = await request.json() return web.json_response(result) @@ -286,8 +296,10 @@ async def cred_def_get(request: web.BaseRequest): context: AdminRequestContext = request["context"] anon_creds_registry = context.inject(AnonCredsRegistry) credential_id = request.match_info["cred_def_id"] - result = await anon_creds_registry.get_credential_definition(context.profile, credential_id) - return web.json_response(result) + result = await anon_creds_registry.get_credential_definition( + context.profile, credential_id + ) + return web.json_response(result.serialize()) @docs(tags=["anoncreds"], summary="") From 88b25f403ccf9d63b337a100118ab58bd1211879 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Wed, 22 Mar 2023 16:09:59 -0600 Subject: [PATCH 070/150] feat: get_schemas and get_credential_definitions implementations Signed-off-by: Char Howland --- .../anoncreds/anoncreds/anoncreds_registry.py | 39 ++++++++------- .../anoncreds/anoncreds/base_registry.py | 9 ++-- .../default/legacy_indy_registry/registry.py | 47 ++++++++++++++++++- .../anoncreds/anoncreds/routes.py | 6 ++- 4 files changed, 77 insertions(+), 24 deletions(-) diff --git a/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py b/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py index 3aad6cbb4c..d4428aae1f 100644 --- a/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py @@ -8,17 +8,22 @@ from ..models.anoncreds_cred_def import ( AnonCredsRegistryGetCredentialDefinition, AnonCredsRegistryGetRevocationList, - AnonCredsRegistryGetRevocationRegistryDefinition) + AnonCredsRegistryGetRevocationRegistryDefinition, +) from ..models.anoncreds_schema import AnonCredsRegistryGetSchema -from .base_registry import (AnonCredsObjectNotFound, - AnonCredsRegistrationFailed, BaseAnonCredsError, - BaseAnonCredsHandler, BaseAnonCredsRegistrar, - BaseAnonCredsResolver) +from .base_registry import ( + AnonCredsObjectNotFound, + AnonCredsRegistrationFailed, + BaseAnonCredsError, + BaseAnonCredsHandler, + BaseAnonCredsRegistrar, + BaseAnonCredsResolver, +) LOGGER = logging.getLogger(__name__) -class AnonCredsRegistry(): +class AnonCredsRegistry: """AnonCredsRegistry""" def __init__(self, registries: Optional[List[BaseAnonCredsHandler]] = None): @@ -28,7 +33,7 @@ def __init__(self, registries: Optional[List[BaseAnonCredsHandler]] = None): if registries: for registry in registries: self.register(registry) - + def register(self, registry: BaseAnonCredsHandler): """Register a new registry.""" if isinstance(registry, BaseAnonCredsResolver): @@ -53,7 +58,9 @@ async def _registrars_for_identifiers(self, identifier: str): async def setup(self, context: InjectionContext): """Setup method.""" - async def get_schema(self, profile: Profile, schema_id: str) -> AnonCredsRegistryGetSchema: + async def get_schema( + self, profile: Profile, schema_id: str + ) -> AnonCredsRegistryGetSchema: """Get a schema from the registry.""" for resolver in await self._resolvers_for_identifier(schema_id): try: @@ -63,14 +70,12 @@ async def get_schema(self, profile: Profile, schema_id: str) -> AnonCredsRegistr raise AnonCredsObjectNotFound(f"{schema_id} could not be resolved") - async def get_schemas(self,filter : dict): - """Get schema id's from the registry.""" + async def get_schemas(self, profile: Profile, filter: dict): + """Get schema ids from the registry.""" results = [ - resolver.get_schemas(filter) - for resolver in self.resolvers + await resolver.get_schemas(profile, filter) for resolver in self.resolvers ] return itertools.chain.from_iterable(results) - # TODO: determine keyword arguments async def register_schema(self, profile: Profile, options, schema): @@ -99,13 +104,11 @@ async def get_credential_definition( raise AnonCredsObjectNotFound( f"{credential_definition_id} could not be resolved" ) - - async def get_credential_definitions( - self, filter : dict - ): + + async def get_credential_definitions(self, profile: Profile, filter: dict): """Get credential definitions id's from the registry.""" results = [ - resolver.get_credential_definitions(filter) + await resolver.get_credential_definitions(profile, filter) for resolver in self.resolvers ] return itertools.chain.from_iterable(results) diff --git a/aries_cloudagent/anoncreds/anoncreds/base_registry.py b/aries_cloudagent/anoncreds/anoncreds/base_registry.py index 01c8e5030f..4eb8ad2386 100644 --- a/aries_cloudagent/anoncreds/anoncreds/base_registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/base_registry.py @@ -3,11 +3,12 @@ from typing import Pattern from ...config.injection_context import InjectionContext +from ...core.profile import Profile from ..models.anoncreds_cred_def import ( AnonCredsRegistryGetCredentialDefinition, + AnonCredsRegistryGetCredentialDefinitions, AnonCredsRegistryGetRevocationList, AnonCredsRegistryGetRevocationRegistryDefinition, - AnonCredsRegistryGetCredentialDefinitions, AnonCredsRegistryGetRevocationRegistryDefinitions, ) from ..models.anoncreds_schema import ( @@ -53,7 +54,9 @@ async def get_schema(self, schema_id: str) -> AnonCredsRegistryGetSchema: """Get a schema from the registry.""" @abstractmethod - async def get_schemas(self, filter: dict) -> AnonCredsRegistryGetSchemas: + async def get_schemas( + self, profile: Profile, filter: dict + ) -> AnonCredsRegistryGetSchemas: """Get a schema ids from the registry.""" @abstractmethod @@ -64,7 +67,7 @@ async def get_credential_definition( @abstractmethod async def get_credential_definitions( - self, filter: dict + self, profile: Profile, filter: dict ) -> AnonCredsRegistryGetCredentialDefinitions: """Get a credential definition ids from the registry.""" diff --git a/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py index db1b0f2614..66040cdbf6 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py @@ -93,9 +93,27 @@ async def get_schema( raise AnonCredsResolutionFailed(err) return anoncreds_registry_get_schema - async def get_schemas(self, profile: Profile, filter: str): + async def get_schemas(self, profile: Profile, filter: dict): """Get schema ids filtered by filter""" + tag_query = {} + schema_issuer_did = filter.get("issuerId") + schema_name = filter.get("name") + schema_version = filter.get("version") + + if schema_issuer_did: + tag_query["schema_issuer_did"] = schema_issuer_did + if schema_name: + tag_query["schema_name"] = schema_name + if schema_version: + tag_query["schema_version"] = schema_version + + session = await profile.session() + storage = session.inject(BaseStorage) + return await storage.find_all_records( + type_filter=SCHEMA_SENT_RECORD_TYPE, tag_query=tag_query + ) + async def register_schema( self, profile: Profile, @@ -214,6 +232,33 @@ async def get_credential_definition( async def get_credential_definitions(self, profile: Profile, filter: str): """Get credential definition ids filtered by filter""" + tag_query = {} + cred_def_id = filter.get("cred_def_id") + issuer_id = filter.get("issuer_id") + schema_id = filter.get("schema_id") + schema_issuer_id = filter.get("schema_issuer_id") + schema_name = filter.get("schema_name") + schema_version = filter.get("schema_version") + + if cred_def_id: + tag_query["cred_def_id"] = cred_def_id + if issuer_id: + tag_query["issuer_id"] = issuer_id + if schema_id: + tag_query["schema_id"] = schema_id + if schema_issuer_id: + tag_query["schema_issuer_id"] = schema_issuer_id + if schema_name: + tag_query["schema_name"] = schema_name + if schema_version: + tag_query["schema_version"] = schema_version + + session = await profile.session() + storage = session.inject(BaseStorage) + return await storage.find_all_records( + type_filter=SCHEMA_SENT_RECORD_TYPE, tag_query=tag_query + ) + async def register_credential_definition( self, profile: Profile, diff --git a/aries_cloudagent/anoncreds/anoncreds/routes.py b/aries_cloudagent/anoncreds/anoncreds/routes.py index 98c5400d13..3532bf4c4a 100644 --- a/aries_cloudagent/anoncreds/anoncreds/routes.py +++ b/aries_cloudagent/anoncreds/anoncreds/routes.py @@ -241,7 +241,7 @@ async def schemas_get(request: web.BaseRequest): "name": schema_name, "version": schema_version, } - schema_ids = anon_creds_registry.get_schemas(filter) + schema_ids = await anon_creds_registry.get_schemas(context.profile, filter) return web.json_response(schema_ids) @@ -325,7 +325,9 @@ async def cred_defs_get(request: web.BaseRequest): "schema_name": query.get("schemaName"), "schema_version": query.get("schemaVersion"), } - cred_def_ids = anon_creds_registry.get_credential_definitions(filter) + cred_def_ids = await anon_creds_registry.get_credential_definitions( + context.profile, filter + ) return web.json_response(cred_def_ids) From a609352b68e669bd51b16f27b202a9040738a0cd Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Thu, 23 Mar 2023 15:43:57 -0400 Subject: [PATCH 071/150] fix: several small adjustments Signed-off-by: Daniel Bluhm --- .../anoncreds/anoncreds/anoncreds_registry.py | 10 +- .../anoncreds/anoncreds/base_registry.py | 3 +- .../anoncreds/anoncreds/holder.py | 4 +- .../anoncreds/anoncreds/issuer.py | 104 +++++++++++------- aries_cloudagent/anoncreds/holder.py | 35 +++++- aries_cloudagent/anoncreds/issuer.py | 10 ++ .../anoncreds/models/anoncreds_schema.py | 78 +++++++++---- aries_cloudagent/anoncreds/sdk/holder.py | 16 ++- 8 files changed, 185 insertions(+), 75 deletions(-) diff --git a/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py b/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py index bb3f1e4244..0ff68ce169 100644 --- a/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py @@ -47,7 +47,7 @@ async def _resolvers_for_identifier(self, identifier: str): if await resolver.supports(identifier) ] - async def _registrars_for_identifiers(self, identifier: str): + async def _registrars_for_identifier(self, identifier: str): return [ registrar for registrar in self.registrars @@ -75,7 +75,7 @@ async def get_schemas(self, filter: dict): # TODO: determine keyword arguments async def register_schema(self, profile, options, schema): """Register a schema on the registry.""" - for registrar in await self._registrars_for_identifiers(schema.issuer_id): + for registrar in await self._registrars_for_identifier(schema.issuer_id): try: return await registrar.register_schema(profile, options, schema) except BaseAnonCredsError: @@ -109,7 +109,7 @@ async def get_credential_definitions(self, filter: dict): # TODO: determine keyword arguments async def register_credential_definition(self): """Register a credential definition on the registry.""" - for registrar in await self._registrars_for_identifiers("something"): + for registrar in await self._registrars_for_identifier("something"): try: return await registrar.register_credential_definition() except BaseAnonCredsError: @@ -136,7 +136,7 @@ async def get_revocation_registry_definition( # TODO: determine keyword arguments async def register_revocation_registry_definition(self): """Register a revocation registry definition on the registry.""" - for registrar in await self._registrars_for_identifiers("something"): + for registrar in await self._registrars_for_identifier("something"): try: return await registrar.register_revocation_registry_definition() except BaseAnonCredsError: @@ -163,7 +163,7 @@ async def get_revocation_list( # TODO: determine keyword arguments async def register_revocation_list(self): """Register a revocation list on the registry.""" - for registrar in await self._registrars_for_identifiers("something"): + for registrar in await self._registrars_for_identifier("something"): try: return await registrar.register_revocation_registry_definition() except BaseAnonCredsError: diff --git a/aries_cloudagent/anoncreds/anoncreds/base_registry.py b/aries_cloudagent/anoncreds/anoncreds/base_registry.py index 4d17911e15..3414b741ac 100644 --- a/aries_cloudagent/anoncreds/anoncreds/base_registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/base_registry.py @@ -13,6 +13,7 @@ from ..models.anoncreds_schema import ( AnonCredsRegistryGetSchema, AnonCredsRegistryGetSchemas, + SchemaResult, ) @@ -86,7 +87,7 @@ async def get_revocation_list( class BaseAnonCredsRegistrar(BaseAnonCredsHandler): # TODO: determine keyword arguments @abstractmethod - async def register_schema(self): + async def register_schema(self) -> SchemaResult: """Register a schema on the registry.""" # TODO: determine keyword arguments diff --git a/aries_cloudagent/anoncreds/anoncreds/holder.py b/aries_cloudagent/anoncreds/anoncreds/holder.py index 5ef3ae39e8..05e6cfd30f 100644 --- a/aries_cloudagent/anoncreds/anoncreds/holder.py +++ b/aries_cloudagent/anoncreds/anoncreds/holder.py @@ -6,7 +6,7 @@ import re import uuid -from typing import Dict, Sequence, Tuple, Union +from typing import Dict, Optional, Sequence, Tuple, Union from aries_askar import AskarError, AskarErrorCode from anoncreds import ( @@ -286,7 +286,7 @@ async def get_credentials_for_presentation_request_by_referent( referents: Sequence[str], start: int, count: int, - extra_query: dict = {}, + extra_query: Optional[dict] = None, ): """ Get credentials stored in the wallet. diff --git a/aries_cloudagent/anoncreds/anoncreds/issuer.py b/aries_cloudagent/anoncreds/anoncreds/issuer.py index 5e92df351d..85c397e8a6 100644 --- a/aries_cloudagent/anoncreds/anoncreds/issuer.py +++ b/aries_cloudagent/anoncreds/anoncreds/issuer.py @@ -2,6 +2,7 @@ import asyncio import logging +import time from typing import Sequence, Tuple @@ -16,9 +17,12 @@ RevocationRegistry, RevocationRegistryDefinition, RevocationRegistryDelta, + RevocationStatusList, Schema, ) +from aries_cloudagent.anoncreds.anoncreds.anoncreds_registry import AnonCredsRegistry + from ...askar.profile import AskarProfile from ..issuer import ( @@ -35,14 +39,14 @@ CATEGORY_CRED_DEF_PRIVATE = "credential_def_private" CATEGORY_CRED_DEF_KEY_PROOF = "credential_def_key_proof" CATEGORY_SCHEMA = "schema" -CATEGORY_REV_REG = "revocation_reg" +CATEGORY_REV_STATUS_LIST = "revocation_status_list" CATEGORY_REV_REG_INFO = "revocation_reg_info" CATEGORY_REV_REG_DEF = "revocation_reg_def" CATEGORY_REV_REG_DEF_PRIVATE = "revocation_reg_def_private" CATEGORY_REV_REG_ISSUER = "revocation_reg_def_issuer" -class IndyCredxIssuer(AnonCredsIssuer): +class AnonCredsRsIssuer(AnonCredsIssuer): """Indy-Credx issuer class.""" def __init__(self, profile: AskarProfile): @@ -85,15 +89,30 @@ async def create_schema( schema_name, schema_version, origin_did, attribute_names ) schema_id = f"{origin_did}:2:{schema_name}:{schema_version}" - schema_json = schema.to_json() - async with self._profile.session() as session: - await session.handle.insert(CATEGORY_SCHEMA, schema_id, schema_json) + anoncreds_registry = self._profile.inject(AnonCredsRegistry) + + schema_result = await anoncreds_registry.register_schema( + origin_did, schema_name, schema_version, attribute_names + ) + if schema_result.schema_state.state == "finished": + await self.store_schema(schema_id, schema_result.schema_state.schema) + except AnoncredsError as err: raise AnonCredsIssuerError("Error creating schema") from err except AskarError as err: raise AnonCredsIssuerError("Error storing schema") from err return (schema_id, schema_json) + async def store_schema( + self, + schema_id: str, + schema: Schema, + ): + """Store schema after reaching finished state.""" + schema_json = schema.to_json() + async with self._profile.session() as session: + await session.handle.insert(CATEGORY_SCHEMA, schema_id, schema_json) + async def credential_definition_in_wallet( self, credential_definition_id: str ) -> bool: @@ -287,7 +306,9 @@ async def create_credential( if revoc_reg_id: try: async with self._profile.transaction() as txn: - rev_reg = await txn.handle.fetch(CATEGORY_REV_REG, revoc_reg_id) + rev_status_list = await txn.handle.fetch( + CATEGORY_REV_STATUS_LIST, revoc_reg_id + ) rev_reg_info = await txn.handle.fetch( CATEGORY_REV_REG_INFO, revoc_reg_id, for_update=True ) @@ -297,7 +318,7 @@ async def create_credential( rev_key = await txn.handle.fetch( CATEGORY_REV_REG_DEF_PRIVATE, revoc_reg_id ) - if not rev_reg: + if not rev_status_list: raise AnonCredsIssuerError("Revocation registry not found") if not rev_reg_info: raise AnonCredsIssuerError( @@ -343,9 +364,7 @@ async def create_credential( revoc = CredentialRevocationConfig( rev_reg_def, rev_key.raw_value, - rev_reg.raw_value, rev_reg_index, - rev_info.get("used_ids") or [], tails_file_path, ) credential_revocation_id = str(rev_reg_index) @@ -354,20 +373,19 @@ async def create_credential( credential_revocation_id = None try: - ( - credential, - _upd_rev_reg, - _delta, - ) = await asyncio.get_event_loop().run_in_executor( - None, - Credential.create, - cred_def.raw_value, - cred_def_private.raw_value, - credential_offer, - credential_request, - raw_values, + credential = await asyncio.get_event_loop().run_in_executor( None, - revoc, + lambda: Credential.create( + cred_def.raw_value, + cred_def_private.raw_value, + credential_offer, + credential_request, + raw_values, + None, + revoc_reg_id, + rev_status_list, + revoc, + ), ) except AnoncredsError as err: raise AnonCredsIssuerError("Error creating credential") from err @@ -409,7 +427,9 @@ async def revoke_credentials( rev_reg_def = await session.handle.fetch( CATEGORY_REV_REG_DEF, revoc_reg_id ) - rev_reg = await session.handle.fetch(CATEGORY_REV_REG, revoc_reg_id) + rev_status_list = await session.handle.fetch( + CATEGORY_REV_STATUS_LIST, revoc_reg_id + ) rev_reg_info = await session.handle.fetch( CATEGORY_REV_REG_INFO, revoc_reg_id ) @@ -417,7 +437,7 @@ async def revoke_credentials( raise AnonCredsIssuerError( "Revocation registry definition not found" ) - if not rev_reg: + if not rev_status_list: raise AnonCredsIssuerError("Revocation registry not found") if not rev_reg_info: raise AnonCredsIssuerError("Revocation registry metadata not found") @@ -472,18 +492,18 @@ async def revoke_credentials( break try: - rev_reg = RevocationRegistry.load(rev_reg.raw_value) + rev_status_list = RevocationStatusList.load(rev_status_list.raw_value) except AnoncredsError as err: raise AnonCredsIssuerError("Error loading revocation registry") from err try: delta = await asyncio.get_event_loop().run_in_executor( None, - lambda: rev_reg.update( - rev_reg_def, + lambda: rev_status_list.update( + int(time.time()), None, # issued list(rev_crids), # revoked - tails_file_path, + rev_reg_def, ), ) except AnoncredsError as err: @@ -493,13 +513,13 @@ async def revoke_credentials( try: async with self._profile.transaction() as txn: - rev_reg_upd = await txn.handle.fetch( - CATEGORY_REV_REG, revoc_reg_id, for_update=True + rev_status_list_upd = await txn.handle.fetch( + CATEGORY_REV_STATUS_LIST, revoc_reg_id, for_update=True ) rev_info_upd = await txn.handle.fetch( CATEGORY_REV_REG_INFO, revoc_reg_id, for_update=True ) - if not rev_reg_upd or not rev_reg_info: + if not rev_status_list_upd or not rev_reg_info: LOGGER.warn( "Revocation registry missing, skipping update: {}", revoc_reg_id, @@ -511,7 +531,9 @@ async def revoke_credentials( # handle concurrent update to the registry by retrying continue await txn.handle.replace( - CATEGORY_REV_REG, revoc_reg_id, rev_reg.to_json_buffer() + CATEGORY_REV_STATUS_LIST, + revoc_reg_id, + rev_status_list.to_json_buffer(), ) used_ids.update(rev_crids) rev_info_upd["used_ids"] = sorted(used_ids) @@ -582,6 +604,8 @@ async def create_and_store_revocation_registry( A tuple of the revocation registry ID, JSON, and entry JSON """ + # TODO Passed in by caller? + rev_reg_def_id = f"{origin_did}:4:{cred_def_id}:CL_ACCUM:{tag}" try: async with self._profile.session() as session: cred_def = await session.handle.fetch(CATEGORY_CRED_DEF, cred_def_id) @@ -598,29 +622,33 @@ async def create_and_store_revocation_registry( ( rev_reg_def, rev_reg_def_private, - rev_reg, - _rev_reg_delta, ) = await asyncio.get_event_loop().run_in_executor( None, lambda: RevocationRegistryDefinition.create( cred_def_id, cred_def.raw_value, + origin_did, tag, revoc_def_type, max_cred_num, tails_dir_path=tails_base_path, ), ) + + rev_status_list = RevocationStatusList.create( + rev_reg_def_id, rev_reg_def, origin_did, int(time.time()) + ) except AnoncredsError as err: raise AnonCredsIssuerError("Error creating revocation registry") from err - rev_reg_def_id = rev_reg_def.id rev_reg_def_json = rev_reg_def.to_json() - rev_reg_json = rev_reg.to_json() + rev_status_list_json = rev_status_list.to_json() try: async with self._profile.transaction() as txn: - await txn.handle.insert(CATEGORY_REV_REG, rev_reg_def_id, rev_reg_json) + await txn.handle.insert( + CATEGORY_REV_STATUS_LIST, rev_reg_def_id, rev_status_list_json + ) await txn.handle.insert( CATEGORY_REV_REG_INFO, rev_reg_def_id, @@ -641,5 +669,5 @@ async def create_and_store_revocation_registry( return ( rev_reg_def_id, rev_reg_def_json, - rev_reg_json, + rev_status_list_json, ) diff --git a/aries_cloudagent/anoncreds/holder.py b/aries_cloudagent/anoncreds/holder.py index f2923e0fbc..654e6f67f8 100644 --- a/aries_cloudagent/anoncreds/holder.py +++ b/aries_cloudagent/anoncreds/holder.py @@ -1,7 +1,7 @@ """Base Indy Holder class.""" from abc import ABC, ABCMeta, abstractmethod -from typing import Tuple, Union +from typing import Optional, Sequence, Tuple, Union from ..core.error import BaseError from ..ledger.base import BaseLedger @@ -75,6 +75,39 @@ async def get_mime_type( """ + @abstractmethod + async def get_credentials(self, start: int, count: int, wql: dict): + """ + Get credentials stored in the wallet. + + Args: + start: Starting index + count: Number of records to return + wql: wql query dict + + """ + + @abstractmethod + async def get_credentials_for_presentation_request_by_referent( + self, + presentation_request: dict, + referents: Sequence[str], + start: int, + count: int, + extra_query: Optional[dict] = None, + ): + """ + Get credentials stored in the wallet. + + Args: + presentation_request: Valid presentation request from issuer + referents: Presentation request referents to use to search for creds + start: Starting index + count: Maximum number of records to return + extra_query: wql query dict + + """ + @abstractmethod async def create_presentation( self, diff --git a/aries_cloudagent/anoncreds/issuer.py b/aries_cloudagent/anoncreds/issuer.py index 49704626ca..27407f58c9 100644 --- a/aries_cloudagent/anoncreds/issuer.py +++ b/aries_cloudagent/anoncreds/issuer.py @@ -3,6 +3,8 @@ from abc import ABC, ABCMeta, abstractmethod from typing import Sequence, Tuple +from anoncreds import Schema + from ..core.error import BaseError @@ -58,6 +60,14 @@ async def create_schema( """ + @abstractmethod + async def store_schema( + self, + schema_id: str, + schema: Schema, + ) -> Tuple[str, str]: + """Store a schema in the wallet.""" + @staticmethod def make_credential_definition_id( origin_did: str, schema: dict, signature_type: str = None, tag: str = None diff --git a/aries_cloudagent/anoncreds/models/anoncreds_schema.py b/aries_cloudagent/anoncreds/models/anoncreds_schema.py index dc030fb264..79ce2593a2 100644 --- a/aries_cloudagent/anoncreds/models/anoncreds_schema.py +++ b/aries_cloudagent/anoncreds/models/anoncreds_schema.py @@ -1,8 +1,9 @@ """Anoncreds Schema OpenAPI validators""" -from typing import Any, Dict, List +from typing import Any, Dict, List, Mapping, Optional, Union from marshmallow import EXCLUDE, fields +from marshmallow.validate import OneOf from aries_cloudagent.anoncreds.models.anoncreds_valid import ( ANONCREDS_SCHEMA_ID, @@ -15,7 +16,7 @@ class AnonCredsSchema(BaseModel): - """AnonCredsSchema""" + """An AnonCreds Schema object.""" class Meta: """AnonCredsSchema metadata.""" @@ -33,7 +34,7 @@ def __init__( class AnonCredsSchemaSchema(BaseModelSchema): - """Marshmallow schema for indy schema.""" + """Marshmallow schema for anoncreds schema.""" class Meta: """AnonCredsSchemaSchema metadata.""" @@ -61,10 +62,10 @@ class Meta: class AnonCredsRegistryGetSchema(BaseModel): - """AnonCredsRegistryGetSchema""" + """Result of resolving a schema.""" class Meta: - """IndyCredInfo metadata.""" + """AnonCredsRegistryGetSchema metadata.""" schema_class = "AnonCredsRegistryGetSchemaSchema" @@ -100,18 +101,8 @@ class Meta: schema_metadata = fields.Dict() -class SchemaState(OpenAPISchema): - """Parameters and validators for schema state.""" - - state = fields.Str() # TODO: create validator for only possible states - schema_id = fields.Str( - data_key="schemaId", description="Schema identifier", **ANONCREDS_SCHEMA_ID - ) - schema_ = fields.Nested(AnonCredsSchemaSchema(), data_key="schema") - - class AnonCredsRegistryGetSchemas(BaseModel): - """SchemasResponAnonCredsRegistryGetSchemasseSchema""" + """Result of retrieving created schemas.""" class Meta: """IndyCredInfo metadata.""" @@ -139,13 +130,62 @@ class Meta: ) -class PostSchemaResponseSchema(OpenAPISchema): +class SchemaState(BaseModel): + """Model representing the state of a schema after beginning registration.""" + + class Meta: + """SchemaState metadata.""" + + schema_class = "SchemaStateSchema" + + def __init__(self, state: str, schema_id: str, schema: AnonCredsSchema, **kwargs): + """Initialize a new SchemaState.""" + super().__init__(**kwargs) + self.state = state + self.schema_id = schema_id + self.schema = schema + + +class SchemaStateSchema(BaseModelSchema): + """Parameters and validators for schema state.""" + + state = fields.Str(validate=OneOf(["finished", "failed", "action", "wait"])) + schema_id = fields.Str( + data_key="schemaId", description="Schema identifier", **ANONCREDS_SCHEMA_ID + ) + schema_ = fields.Nested(AnonCredsSchemaSchema(), data_key="schema") + + +class SchemaResult(BaseModel): + """Result of registering a schema.""" + + class Meta: + """SchemaResult metadata.""" + + schema_class = "SchemaResultSchema" + + def __init__( + self, + job_id: Optional[str], + schema_state: SchemaState, + registration_metadata: Optional[dict] = None, + schema_metadata: Optional[dict] = None, + **kwargs + ): + super().__init__(**kwargs) + self.job_id = job_id + self.schema_state = schema_state + self.registration_metadata = registration_metadata + self.schema_metadata = schema_metadata + + +class SchemaResultSchema(BaseModelSchema): """Parameters and validators for schema state.""" job_id = fields.Str() - schema_state = fields.Nested(SchemaState()) - # For indy, schema_metadata will contain the seqNo + schema_state = fields.Nested(SchemaStateSchema()) registration_metadata = fields.Dict() + # For indy, schema_metadata will contain the seqNo schema_metadata = fields.Dict() diff --git a/aries_cloudagent/anoncreds/sdk/holder.py b/aries_cloudagent/anoncreds/sdk/holder.py index fb3e9e8825..bb21661909 100644 --- a/aries_cloudagent/anoncreds/sdk/holder.py +++ b/aries_cloudagent/anoncreds/sdk/holder.py @@ -5,7 +5,7 @@ import re from collections import OrderedDict -from typing import Sequence, Tuple, Union +from typing import Optional, Sequence, Tuple, Union import indy.anoncreds from indy.error import ErrorCode, IndyError @@ -189,7 +189,7 @@ async def get_credentials_for_presentation_request_by_referent( referents: Sequence[str], start: int, count: int, - extra_query: dict = {}, + extra_query: Optional[dict] = None, ): """ Get credentials stored in the wallet. @@ -226,13 +226,11 @@ async def fetch(reft, limit): with IndyErrorHandler( "Error when constructing wallet credential query", AnonCredsHolderError ): - search_handle = ( - await ( - indy.anoncreds.prover_search_credentials_for_proof_req( - self.wallet.handle, - json.dumps(presentation_request), - json.dumps(extra_query), - ) + search_handle = await ( + indy.anoncreds.prover_search_credentials_for_proof_req( + self.wallet.handle, + json.dumps(presentation_request), + json.dumps(extra_query if extra_query else {}), ) ) From af068525bc3a0610f711d01fa85bb2c0486ce38d Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Fri, 24 Mar 2023 16:13:07 -0400 Subject: [PATCH 072/150] feat: updates to register_schema Signed-off-by: Daniel Bluhm --- .../anoncreds/anoncreds/anoncreds_registry.py | 147 ++++++---------- .../anoncreds/anoncreds/base_registry.py | 53 +++++- .../default/legacy_indy_registry/registry.py | 117 ++++++------- .../anoncreds/anoncreds/issuer.py | 100 +++++++++-- .../anoncreds/anoncreds/routes.py | 53 +++--- aries_cloudagent/anoncreds/issuer.py | 18 +- .../anoncreds/models/anoncreds_schema.py | 44 +++-- aries_cloudagent/askar/profile.py | 3 +- aries_cloudagent/ledger/base.py | 159 ++++++++---------- aries_cloudagent/ledger/error.py | 19 +++ 10 files changed, 392 insertions(+), 321 deletions(-) diff --git a/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py b/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py index 851accfdec..4d6ffd67dc 100644 --- a/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py @@ -1,7 +1,7 @@ """AnonCreds Registry""" import itertools import logging -from typing import List, Optional +from typing import List, Optional, Sequence from ...config.injection_context import InjectionContext from ...core.profile import Profile @@ -10,10 +10,11 @@ AnonCredsRegistryGetRevocationList, AnonCredsRegistryGetRevocationRegistryDefinition, ) -from ..models.anoncreds_schema import AnonCredsRegistryGetSchema +from ..models.anoncreds_schema import AnonCredsRegistryGetSchema, SchemaResult from .base_registry import ( AnonCredsObjectNotFound, - AnonCredsRegistrationFailed, + AnonCredsRegistrationError, + AnonCredsResolutionError, BaseAnonCredsError, BaseAnonCredsHandler, BaseAnonCredsRegistrar, @@ -41,19 +42,29 @@ def register(self, registry: BaseAnonCredsHandler): if isinstance(registry, BaseAnonCredsRegistrar): self.registrars.append(registry) - async def _resolvers_for_identifier(self, identifier: str): - return [ + async def _resolver_for_identifier(self, identifier: str): + resolvers = [ resolver for resolver in self.resolvers if await resolver.supports(identifier) ] - - async def _registrars_for_identifier(self, identifier: str): - return [ + if len(resolvers) > 1: + raise AnonCredsResolutionError( + f"More than one resolver found for identifier {identifier}" + ) + return resolvers[0] + + async def _registrar_for_identifier(self, identifier: str): + registrars = [ registrar for registrar in self.registrars if await registrar.supports(identifier) ] + if len(registrars) > 1: + raise AnonCredsRegistrationError( + f"More than one registrar found for identifier {identifier}" + ) + return registrars[0] async def setup(self, context: InjectionContext): """Setup method.""" @@ -62,47 +73,32 @@ async def get_schema( self, profile: Profile, schema_id: str ) -> AnonCredsRegistryGetSchema: """Get a schema from the registry.""" - for resolver in await self._resolvers_for_identifier(schema_id): - try: - return await resolver.get_schema(profile, schema_id) - except BaseAnonCredsError: - LOGGER.exception("Error getting schema from resolver") - - raise AnonCredsObjectNotFound(f"{schema_id} could not be resolved") - - async def get_schemas(self, profile: Profile, filter: dict): - """Get schema ids from the registry.""" - results = [ - await resolver.get_schemas(profile, filter) for resolver in self.resolvers - ] - return itertools.chain.from_iterable(results) + resolver = await self._resolver_for_identifier(schema_id) + return await resolver.get_schema(profile, schema_id) - # TODO: determine keyword arguments - async def register_schema(self, profile: Profile, options, schema): + async def register_schema( + self, + profile: Profile, + issuer_id: str, + name: str, + version: str, + attr_names: Sequence[str], + options: Optional[dict] = None, + ) -> SchemaResult: """Register a schema on the registry.""" - for registrar in await self._registrars_for_identifier(schema.issuer_id): - try: - return await registrar.register_schema(profile, options, schema) - except BaseAnonCredsError: - LOGGER.exception("Error registering schema with registrar") - - raise AnonCredsRegistrationFailed("Failed to register schema") + registrar = await self._registrar_for_identifier(issuer_id) + return await registrar.register_schema( + profile, issuer_id, name, version, attr_names, options + ) async def get_credential_definition( self, profile: Profile, credential_definition_id: str ) -> AnonCredsRegistryGetCredentialDefinition: """Get a credential definition from the registry.""" - for resolver in await self._resolvers_for_identifier(credential_definition_id): - try: - return await resolver.get_credential_definition( - profile, - credential_definition_id, - ) - except BaseAnonCredsError: - LOGGER.exception("Error getting credential definition from resolver") - - raise AnonCredsObjectNotFound( - f"{credential_definition_id} could not be resolved" + resolver = await self._resolver_for_identifier(credential_definition_id) + return await resolver.get_credential_definition( + profile, + credential_definition_id, ) async def get_credential_definitions(self, profile: Profile, filter: dict): @@ -124,71 +120,38 @@ async def register_credential_definition( issuer_id: str, ): """Register a credential definition on the registry.""" - for registrar in await self._registrars_for_identifier("something"): - try: - return await registrar.register_credential_definition( - profile, - schema_id, - support_revocation, - tag, - rev_reg_size, - issuer_id, - ) - except BaseAnonCredsError: - LOGGER.exception("Error registering schema with registrar") - - raise AnonCredsRegistrationFailed("Failed to register credential definition") + registrar = await self._registrar_for_identifier("something") + return await registrar.register_credential_definition( + profile, + schema_id, + support_revocation, + tag, + rev_reg_size, + issuer_id, + ) async def get_revocation_registry_definition( self, revocation_registry_id: str ) -> AnonCredsRegistryGetRevocationRegistryDefinition: """Get a revocation registry definition from the registry.""" - for resolver in await self._resolvers_for_identifier(revocation_registry_id): - try: - return await resolver.get_revocation_registry_definition( - revocation_registry_id - ) - except BaseAnonCredsError: - LOGGER.exception( - "Error getting revocation registry definition from resolver" - ) - - raise AnonCredsObjectNotFound(f"{revocation_registry_id} could not be resolved") + resolver = await self._resolver_for_identifier(revocation_registry_id) + return await resolver.get_revocation_registry_definition(revocation_registry_id) # TODO: determine keyword arguments async def register_revocation_registry_definition(self): """Register a revocation registry definition on the registry.""" - for registrar in await self._registrars_for_identifier("something"): - try: - return await registrar.register_revocation_registry_definition() - except BaseAnonCredsError: - LOGGER.exception("Error registering schema with registrar") - - raise AnonCredsRegistrationFailed( - "Failed to register revocation registry definition" - ) + registrar = await self._registrar_for_identifier("something") + return await registrar.register_revocation_registry_definition() async def get_revocation_list( self, revocation_registry_id: str, timestamp: str ) -> AnonCredsRegistryGetRevocationList: """Get a revocation list from the registry.""" - for resolver in await self._resolvers_for_identifier(revocation_registry_id): - try: - return await resolver.get_revocation_list( - revocation_registry_id, timestamp - ) - except BaseAnonCredsError: - LOGGER.exception("Error getting revocation list from resolver") - - raise AnonCredsObjectNotFound(f"{revocation_registry_id} could not be resolved") + resolver = await self._resolver_for_identifier(revocation_registry_id) + return await resolver.get_revocation_list(revocation_registry_id, timestamp) # TODO: determine keyword arguments async def register_revocation_list(self): """Register a revocation list on the registry.""" - for registrar in await self._registrars_for_identifier("something"): - try: - return await registrar.register_revocation_registry_definition() - except BaseAnonCredsError: - LOGGER.exception("Error registering schema with registrar") - - raise AnonCredsRegistrationFailed("Failed to register revocation list") + registrar = await self._registrar_for_identifier("something") + return await registrar.register_revocation_registry_definition() diff --git a/aries_cloudagent/anoncreds/anoncreds/base_registry.py b/aries_cloudagent/anoncreds/anoncreds/base_registry.py index d3a7f5a00d..68682b2d1d 100644 --- a/aries_cloudagent/anoncreds/anoncreds/base_registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/base_registry.py @@ -1,6 +1,8 @@ """Base Registry""" from abc import ABC, abstractmethod -from typing import Pattern +from typing import Generic, Optional, Pattern, Sequence, Tuple, TypeVar + +from aries_cloudagent.core.error import BaseError from ...config.injection_context import InjectionContext from ...core.profile import Profile @@ -18,7 +20,10 @@ ) -class BaseAnonCredsError(Exception): +T = TypeVar("T") + + +class BaseAnonCredsError(BaseError): """Base error class for AnonCreds.""" @@ -26,11 +31,40 @@ class AnonCredsObjectNotFound(BaseAnonCredsError): """Raised when object is not found in resolver.""" -class AnonCredsRegistrationFailed(BaseAnonCredsError): +class AnonCredsRegistrationError(BaseAnonCredsError): """Raised when registering an AnonCreds object fails.""" -class AnonCredsResolutionFailed(BaseAnonCredsError): +class AnonCredsObjectAlreadyExists(AnonCredsRegistrationError, Generic[T]): + """Raised when an AnonCreds object already exists.""" + + def __init__( + self, message: Optional[str] = None, obj: Optional[T] = None, *args, **kwargs + ): + super().__init__(message, obj, *args, **kwargs) + self.obj = obj + + @property + def message(self): + if self.args[0] and self.args[1]: + return f"{self.args[0]}: {self.args[1]}" + else: + return super().message + + +class AnonCredsSchemaAlreadyExists(AnonCredsObjectAlreadyExists[Tuple[str, dict]]): + """Raised when a schema already exists.""" + + @property + def schema_id(self): + return self.obj[0] + + @property + def schema(self): + return self.obj[1] + + +class AnonCredsResolutionError(BaseAnonCredsError): """Raised when resolving an AnonCreds object fails.""" @@ -92,9 +126,16 @@ async def get_revocation_list( class BaseAnonCredsRegistrar(BaseAnonCredsHandler): - # TODO: determine keyword arguments @abstractmethod - async def register_schema(self) -> SchemaResult: + async def register_schema( + self, + profile: Profile, + issuer_id: str, + name: str, + version: str, + attr_names: Sequence[str], + options: Optional[dict] = None, + ) -> SchemaResult: """Register a schema on the registry.""" # TODO: determine keyword arguments diff --git a/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py index 66040cdbf6..9bc5772d63 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py @@ -2,12 +2,14 @@ import logging import re from asyncio import shield -from typing import Pattern +from typing import Optional, Pattern, Sequence + +from anoncreds import Schema from .....config.injection_context import InjectionContext from .....core.profile import Profile from .....ledger.base import BaseLedger -from .....ledger.error import LedgerError +from .....ledger.error import LedgerError, LedgerObjectAlreadyExistsError from .....ledger.merkel_validation.constants import GET_SCHEMA from .....ledger.multiple_ledger.ledger_requests_executor import ( GET_CRED_DEF, @@ -28,10 +30,17 @@ AnonCredsRegistryGetRevocationList, AnonCredsRegistryGetRevocationRegistryDefinition, ) -from ....models.anoncreds_schema import AnonCredsRegistryGetSchema, AnonCredsSchema +from ....models.anoncreds_schema import ( + AnonCredsRegistryGetSchema, + AnonCredsSchema, + SchemaResult, + SchemaState, +) from ...base_registry import ( - AnonCredsRegistrationFailed, - AnonCredsResolutionFailed, + AnonCredsObjectAlreadyExists, + AnonCredsRegistrationError, + AnonCredsResolutionError, + AnonCredsSchemaAlreadyExists, BaseAnonCredsRegistrar, BaseAnonCredsResolver, ) @@ -72,7 +81,7 @@ async def get_schema( reason = "No ledger available" if not profile.settings.get_value("wallet.type"): reason += ": missing wallet-type?" - raise AnonCredsResolutionFailed(reason) + raise AnonCredsResolutionError(reason) async with ledger: try: @@ -90,7 +99,7 @@ async def get_schema( schema_metadata={"seqNo": schema["seqNo"]}, ) except LedgerError as err: - raise AnonCredsResolutionFailed(err) + raise AnonCredsResolutionError(err) return anoncreds_registry_get_schema async def get_schemas(self, profile: Profile, filter: dict): @@ -117,74 +126,44 @@ async def get_schemas(self, profile: Profile, filter: dict): async def register_schema( self, profile: Profile, - options: dict, - schema, - ): + issuer_id: str, + name: str, + version: str, + attr_names: Sequence[str], + options: Optional[dict] = None, + ) -> SchemaResult: """Register a schema on the registry.""" - # Check that schema doesn't already exist - tag_query = {"schema_name": schema.name, "schema_version": schema.version} - async with profile.session() as session: - storage = session.inject(BaseStorage) - found = await storage.find_all_records( - type_filter=SCHEMA_SENT_RECORD_TYPE, - tag_query=tag_query, - ) - if 0 < len(found): - raise AnonCredsRegistrationFailed( - f"Schema {schema.name} {schema.version} already exists." - ) + + schema = Schema.create(issuer_id, name, version, attr_names) + schema_id = f"{issuer_id}:2:{name}:{version}" # Assume endorser role on the network, no option for 3rd-party endorser ledger = profile.inject_or(BaseLedger) if not ledger: reason = "No ledger available" if not profile.settings.get_value("wallet.type"): + # TODO is this warning necessary? reason += ": missing wallet-type?" - raise AnonCredsRegistrationFailed(reason) + raise AnonCredsRegistrationError(reason) - issuer = profile.inject(AnonCredsIssuer) async with ledger: try: - schema_id, schema_def = await shield( - ledger.create_and_send_schema( - issuer, - schema.name, - schema.version, - schema.attr_names, - write_ledger=True, # TODO: check - endorser_did=schema.issuer_id, - ) - ) + seq_no = await shield(ledger.send_schema(schema_id, schema.to_dict())) + except LedgerObjectAlreadyExistsError as err: + raise AnonCredsSchemaAlreadyExists(err.message, err.obj) except (AnonCredsIssuerError, LedgerError) as err: - raise AnonCredsRegistrationFailed(err) - - meta_data = { - "context": { - "schema_id": schema_id, - "schema_name": schema.name, - "schema_version": schema.version, - "attributes": schema.attr_names, - }, - "processing": {}, - } - # Notify event - await notify_schema_event(profile, schema_id, meta_data) - - return { - "job_id": None, - "schema_state": { - "state": "finished", - "schema_id": schema_id, - "schema": { - "attrNames": schema_def["attrNames"], - "name": schema_def["name"], - "version": schema_def["ver"], - "issuerId": schema.issuer_id, - }, - }, - "registration_metadata": {}, - "schema_metadata": {"seqNo": schema_def["seqNo"]}, - } + raise AnonCredsRegistrationError("Failed to register schema") from err + + return SchemaResult( + job_id=None, + schema_state=SchemaState( + state=SchemaState.STATE_FINISHED, + schema_id=schema_id, + schema=AnonCredsSchema.deserialize(schema.to_dict()), + ), + registration_metadata={}, + schema_metadata={"seqNo": seq_no}, + ) async def get_credential_definition( self, profile: Profile, cred_def_id: str @@ -205,7 +184,7 @@ async def get_credential_definition( reason = "No ledger available" if not profile.settings.get_value("wallet.type"): reason += ": missing wallet-type?" - raise AnonCredsResolutionFailed(reason) + raise AnonCredsResolutionError(reason) async with ledger: cred_def = await ledger.get_credential_definition(cred_def_id) @@ -276,7 +255,7 @@ async def register_credential_definition( reason = "No ledger available" if not profile.settings.get_value("wallet.type"): reason += ": missing wallet-type?" - raise AnonCredsRegistrationFailed(reason) + raise AnonCredsRegistrationError(reason) issuer = profile.inject(AnonCredsIssuer) try: # even if in wallet, send it and raise if erroneously so @@ -294,7 +273,7 @@ async def register_credential_definition( ) except (AnonCredsIssuerError, LedgerError) as e: - raise AnonCredsRegistrationFailed(e) + raise AnonCredsRegistrationError(e) issuer_did = cred_def_id.split(":")[0] meta_data = { @@ -350,7 +329,7 @@ async def get_revocation_registry_definition( revoc = IndyRevocation(profile) rev_reg = await revoc.get_issuer_rev_reg_record(rev_reg_id) except StorageNotFoundError as err: - raise AnonCredsResolutionFailed(err) + raise AnonCredsResolutionError(err) return rev_reg.serialize # use AnonCredsRevocationRegistryDefinition object @@ -378,9 +357,9 @@ async def register_revocation_registry_definition( ) LOGGER.debug("published rev reg definition: %s", rev_reg_id) except StorageNotFoundError as err: - raise AnonCredsRegistrationFailed(err) + raise AnonCredsRegistrationError(err) except RevocationError as err: - raise AnonCredsRegistrationFailed(err) + raise AnonCredsRegistrationError(err) async def get_revocation_list( self, revocation_registry_id: str, timestamp: str diff --git a/aries_cloudagent/anoncreds/anoncreds/issuer.py b/aries_cloudagent/anoncreds/anoncreds/issuer.py index 85c397e8a6..a61ab2cb17 100644 --- a/aries_cloudagent/anoncreds/anoncreds/issuer.py +++ b/aries_cloudagent/anoncreds/anoncreds/issuer.py @@ -4,7 +4,7 @@ import logging import time -from typing import Sequence, Tuple +from typing import Optional, Sequence, Tuple from aries_askar import AskarError @@ -14,14 +14,14 @@ CredentialOffer, CredentialRevocationConfig, AnoncredsError, - RevocationRegistry, RevocationRegistryDefinition, RevocationRegistryDelta, RevocationStatusList, - Schema, ) -from aries_cloudagent.anoncreds.anoncreds.anoncreds_registry import AnonCredsRegistry +from .anoncreds_registry import AnonCredsRegistry +from .base_registry import AnonCredsSchemaAlreadyExists +from ..models.anoncreds_schema import SchemaResult, SchemaState from ...askar.profile import AskarProfile @@ -66,16 +66,17 @@ def profile(self) -> AskarProfile: async def create_schema( self, - origin_did: str, + issuer_id: str, schema_name: str, schema_version: str, attribute_names: Sequence[str], - ) -> Tuple[str, str]: + options: Optional[dict] = None, + ) -> SchemaResult: """ Create a new credential schema and store it in the wallet. Args: - origin_did: the DID issuing the credential definition + issuer_id: the DID issuing the credential definition schema_name: the schema name schema_version: the schema version attribute_names: a sequence of schema attribute names @@ -84,34 +85,97 @@ async def create_schema( A tuple of the schema ID and JSON """ - try: - schema = Schema.create( - schema_name, schema_version, origin_did, attribute_names + # Check if record of a similar schema already exists in our records + async with self._profile.session() as session: + # TODO scan? + schemas = await session.handle.fetch_all( + CATEGORY_SCHEMA, + { + "name": schema_name, + "version": schema_version, + "issuer_id": issuer_id, + }, + limit=1, ) - schema_id = f"{origin_did}:2:{schema_name}:{schema_version}" + if schemas: + raise AnonCredsSchemaAlreadyExists( + f"Schema with {schema_name}: {schema_version} already exists for {issuer_id}" + ) + + try: anoncreds_registry = self._profile.inject(AnonCredsRegistry) schema_result = await anoncreds_registry.register_schema( - origin_did, schema_name, schema_version, attribute_names + self.profile, + issuer_id, + schema_name, + schema_version, + attribute_names, + options, ) - if schema_result.schema_state.state == "finished": - await self.store_schema(schema_id, schema_result.schema_state.schema) + if schema_result.schema_state.state == SchemaState.STATE_FINISHED: + await self.store_schema( + schema_result.schema_state.schema_id, + schema_result.schema_state.schema.serialize(), + ) + + return schema_result + + except AnonCredsSchemaAlreadyExists as err: + # If we find that we've previously written a schema that looks like + # this one before but that schema is not in our wallet, add it to + # the wallet so we can return from our get schema calls + await self.store_schema(err.schema_id, err.schema) + raise AnonCredsIssuerError( + "Schema already exists but was not in wallet; stored in wallet" + ) from err except AnoncredsError as err: raise AnonCredsIssuerError("Error creating schema") from err except AskarError as err: raise AnonCredsIssuerError("Error storing schema") from err - return (schema_id, schema_json) async def store_schema( self, schema_id: str, - schema: Schema, + schema: dict, ): """Store schema after reaching finished state.""" - schema_json = schema.to_json() async with self._profile.session() as session: - await session.handle.insert(CATEGORY_SCHEMA, schema_id, schema_json) + await session.handle.insert( + CATEGORY_SCHEMA, + schema_id, + json.dumps(schema), + { + "name": schema["name"], + "version": schema["version"], + "issuer_id": schema["issuerId"], + }, + ) + + async def get_created_schemas( + self, + name: Optional[str] = None, + version: Optional[str] = None, + issuer_id: Optional[str] = None, + ) -> Sequence[str]: + """Retrieve IDs of schemas previously created.""" + async with self._profile.session() as session: + # TODO limit? scan? + schemas = await session.handle.fetch_all( + CATEGORY_SCHEMA, + { + key: value + for key, value in { + "name": name, + "version": version, + "issuer_id": issuer_id, + } + if value is not None + }, + ) + # schema is an Entry; Entry.name was stored as the schema's ID + return [schema.name for schema in schemas] async def credential_definition_in_wallet( self, credential_definition_id: str diff --git a/aries_cloudagent/anoncreds/anoncreds/routes.py b/aries_cloudagent/anoncreds/anoncreds/routes.py index 3532bf4c4a..d78145b055 100644 --- a/aries_cloudagent/anoncreds/anoncreds/routes.py +++ b/aries_cloudagent/anoncreds/anoncreds/routes.py @@ -11,17 +11,18 @@ ) from marshmallow import fields -from aries_cloudagent.anoncreds.anoncreds.anoncreds_registry import AnonCredsRegistry -from aries_cloudagent.anoncreds.models.anoncreds_cred_def import ( +from .anoncreds_registry import AnonCredsRegistry +from ..issuer import AnonCredsIssuer +from ..models.anoncreds_cred_def import ( AnonCredsCredentialDefinitionSchema, AnonCredsRegistryGetCredentialDefinitionSchema, ) -from aries_cloudagent.anoncreds.models.anoncreds_schema import ( +from ..models.anoncreds_schema import ( AnonCredsRegistryGetSchemasSchema, AnonCredsSchema, - PostSchemaResponseSchema, - SchemaPostQueryStringSchema, - AnonCredsRegistryGetSchemaSchema, + SchemaResultSchema, + SchemaPostRequestSchema, + GetSchemaResultSchema, SchemasQueryStringSchema, ) from aries_cloudagent.anoncreds.models.anoncreds_valid import ( @@ -150,8 +151,8 @@ class GetCredDefsResponseSchema(OpenAPISchema): @docs(tags=["anoncreds"], summary="") -@request_schema(SchemaPostQueryStringSchema()) -@response_schema(PostSchemaResponseSchema(), 200, description="") +@request_schema(SchemaPostRequestSchema()) +@response_schema(SchemaResultSchema(), 200, description="") async def schemas_post(request: web.BaseRequest): """Request handler for creating a schema. @@ -188,20 +189,19 @@ async def schemas_post(request: web.BaseRequest): """ context: AdminRequestContext = request["context"] - anon_creds_registry = context.inject(AnonCredsRegistry) + request_data = await request.json() options = request_data.get("option") schema_data = request_data.get("schema") - schema: AnonCredsSchema = AnonCredsSchema.deserialize(schema_data) - # TODO: serialize return stuff. - result = await anon_creds_registry.register_schema(context.profile, options, schema) - return web.json_response(result) + issuer = context.inject(AnonCredsIssuer) + result = await issuer.create_schema(**schema_data, options=options) + return web.json_response(result.serialize()) @docs(tags=["anoncreds"], summary="") @match_info_schema(SchemaIdMatchInfo()) -@response_schema(AnonCredsRegistryGetSchemaSchema(), 200, description="") +@response_schema(GetSchemaResultSchema(), 200, description="") async def schema_get(request: web.BaseRequest): """Request handler for getting a schema. @@ -213,9 +213,9 @@ async def schema_get(request: web.BaseRequest): """ context: AdminRequestContext = request["context"] - anon_creds_registry = context.inject(AnonCredsRegistry) + anoncreds_registry = context.inject(AnonCredsRegistry) schema_id = request.match_info["schemaId"] - result = await anon_creds_registry.get_schema(context.profile, schema_id) + result = await anoncreds_registry.get_schema(context.profile, schema_id) return web.json_response(result.serialize()) @@ -232,17 +232,16 @@ async def schemas_get(request: web.BaseRequest): """ context: AdminRequestContext = request["context"] - anon_creds_registry = context.inject(AnonCredsRegistry) - schema_issuer_did = request.query.get("schemaIssuerDid") - schema_name = request.query.get("schemaName") - schema_version = request.query.get("schemaVersion") - filter = { - "issuerId": schema_issuer_did, - "name": schema_name, - "version": schema_version, - } - schema_ids = await anon_creds_registry.get_schemas(context.profile, filter) - return web.json_response(schema_ids) + + schema_issuer_id = request.query.get("schema_issuer_id") + schema_name = request.query.get("schema_name") + schema_version = request.query.get("schema_version") + + issuer = context.inject(AnonCredsIssuer) + schema_ids = await issuer.get_created_schemas( + schema_name, schema_version, schema_issuer_id + ) + return web.json_response({"schema_ids": schema_ids}) @docs(tags=["anoncreds"], summary="") diff --git a/aries_cloudagent/anoncreds/issuer.py b/aries_cloudagent/anoncreds/issuer.py index 27407f58c9..a4d42ba257 100644 --- a/aries_cloudagent/anoncreds/issuer.py +++ b/aries_cloudagent/anoncreds/issuer.py @@ -1,10 +1,12 @@ """Base Indy Issuer class.""" from abc import ABC, ABCMeta, abstractmethod -from typing import Sequence, Tuple +from typing import Optional, Sequence, Tuple from anoncreds import Schema +from .models.anoncreds_schema import SchemaResult + from ..core.error import BaseError @@ -41,11 +43,12 @@ def make_schema_id(origin_did: str, schema_name: str, schema_version: str) -> st @abstractmethod async def create_schema( self, - origin_did: str, + issuer_id: str, schema_name: str, schema_version: str, attribute_names: Sequence[str], - ) -> Tuple[str, str]: + options: Optional[dict] = None, + ) -> SchemaResult: """ Create a new credential schema and store it in the wallet. @@ -68,6 +71,15 @@ async def store_schema( ) -> Tuple[str, str]: """Store a schema in the wallet.""" + @abstractmethod + async def get_created_schemas( + self, + name: Optional[str] = None, + version: Optional[str] = None, + issuer_id: Optional[str] = None, + ) -> Sequence[str]: + """Retrieve stored schemas from the wallet.""" + @staticmethod def make_credential_definition_id( origin_did: str, schema: dict, signature_type: str = None, tag: str = None diff --git a/aries_cloudagent/anoncreds/models/anoncreds_schema.py b/aries_cloudagent/anoncreds/models/anoncreds_schema.py index 79ce2593a2..8c4db74388 100644 --- a/aries_cloudagent/anoncreds/models/anoncreds_schema.py +++ b/aries_cloudagent/anoncreds/models/anoncreds_schema.py @@ -7,12 +7,11 @@ from aries_cloudagent.anoncreds.models.anoncreds_valid import ( ANONCREDS_SCHEMA_ID, - ANONCREDS_VERSION, ) from aries_cloudagent.messaging.models.base import BaseModel, BaseModelSchema from ...messaging.models.openapi import OpenAPISchema -from ...messaging.valid import GENERIC_DID, UUIDFour +from ...messaging.valid import UUIDFour class AnonCredsSchema(BaseModel): @@ -58,14 +57,14 @@ class Meta: description="Schema name", example=ANONCREDS_SCHEMA_ID["example"].split(":")[2], ) - version = fields.Str(description="Schema version", **ANONCREDS_VERSION) + version = fields.Str(description="Schema version") -class AnonCredsRegistryGetSchema(BaseModel): +class GetSchemaResult(BaseModel): """Result of resolving a schema.""" class Meta: - """AnonCredsRegistryGetSchema metadata.""" + """GetSchemaResult metadata.""" schema_class = "AnonCredsRegistryGetSchemaSchema" @@ -84,13 +83,13 @@ def __init__( self.schema_metadata = schema_metadata -class AnonCredsRegistryGetSchemaSchema(BaseModelSchema): +class GetSchemaResultSchema(BaseModelSchema): """Parameters and validators for schema create query.""" class Meta: """AnonCredsRegistryGetSchemaSchema metadata.""" - model_class = AnonCredsRegistryGetSchema + model_class = GetSchemaResult unknown = EXCLUDE schema_ = fields.Nested(AnonCredsSchemaSchema(), data_key="schema") @@ -133,6 +132,11 @@ class Meta: class SchemaState(BaseModel): """Model representing the state of a schema after beginning registration.""" + STATE_FINISHED = "finished" + STATE_FAILED = "failed" + STATE_ACTION = "action" + STATE_WAIT = "wait" + class Meta: """SchemaState metadata.""" @@ -149,7 +153,16 @@ def __init__(self, state: str, schema_id: str, schema: AnonCredsSchema, **kwargs class SchemaStateSchema(BaseModelSchema): """Parameters and validators for schema state.""" - state = fields.Str(validate=OneOf(["finished", "failed", "action", "wait"])) + state = fields.Str( + validate=OneOf( + [ + SchemaState.STATE_FINISHED, + SchemaState.STATE_FAILED, + SchemaState.STATE_ACTION, + SchemaState.STATE_WAIT, + ] + ) + ) schema_id = fields.Str( data_key="schemaId", description="Schema identifier", **ANONCREDS_SCHEMA_ID ) @@ -192,28 +205,27 @@ class SchemaResultSchema(BaseModelSchema): class SchemasQueryStringSchema(OpenAPISchema): """Parameters and validators for query string in schemas list query.""" - schemaName = fields.Str( + schema_name = fields.Str( description="Schema name", - example=ANONCREDS_SCHEMA_ID["example"].split(":")[2], + example="example-schema", ) - schemaVersion = fields.Str(description="Schema version", **ANONCREDS_VERSION) - schemaIssuerDid = fields.Str( + schema_version = fields.Str(description="Schema version") + schema_issuer_id = fields.Str( description="Issuer Identifier of the credential definition or schema", - **GENERIC_DID, - ) # TODO: get correct validator + ) class SchemaPostOptionSchema(OpenAPISchema): """Parameters and validators for schema options.""" endorser_connection_id = fields.UUID( - description="Connection identifier (optional)", + description="Connection identifier (optional) (this is an example)", required=False, example=UUIDFour.EXAMPLE, ) -class SchemaPostQueryStringSchema(OpenAPISchema): +class SchemaPostRequestSchema(OpenAPISchema): """Parameters and validators for query string in create schema.""" schema = fields.Nested(AnonCredsSchemaSchema()) diff --git a/aries_cloudagent/askar/profile.py b/aries_cloudagent/askar/profile.py index e9a0a9ecaf..b847dd294f 100644 --- a/aries_cloudagent/askar/profile.py +++ b/aries_cloudagent/askar/profile.py @@ -110,7 +110,8 @@ def bind_providers(self): injector.bind_provider( AnonCredsIssuer, ClassProvider( - "aries_cloudagent.anoncreds.credx.issuer.IndyCredxIssuer", ref(self) + "aries_cloudagent.anoncreds.anoncreds.issuer.AnonCredsRsIssuer", + ref(self), ), ) injector.bind_provider( diff --git a/aries_cloudagent/ledger/base.py b/aries_cloudagent/ledger/base.py index 4948970992..d2cbfe817a 100644 --- a/aries_cloudagent/ledger/base.py +++ b/aries_cloudagent/ledger/base.py @@ -7,7 +7,7 @@ from abc import ABC, abstractmethod, ABCMeta from enum import Enum from hashlib import sha256 -from typing import List, Sequence, Tuple, Union +from typing import List, Optional, Sequence, Tuple, Union from ..anoncreds.issuer import ( DEFAULT_CRED_DEF_TAG, @@ -17,7 +17,12 @@ from ..utils import sentinel from ..wallet.did_info import DIDInfo -from .error import BadLedgerRequestError, LedgerError, LedgerTransactionError +from .error import ( + BadLedgerRequestError, + LedgerError, + LedgerTransactionError, + LedgerObjectAlreadyExistsError, +) from .endpoint_type import EndpointType @@ -255,121 +260,97 @@ async def fetch_schema_by_seq_no(self, seq_no: int) -> dict: async def check_existing_schema( self, - public_did: str, - schema_name: str, - schema_version: str, + schema_id: str, attribute_names: Sequence[str], ) -> Tuple[str, dict]: """Check if a schema has already been published.""" - fetch_schema_id = f"{public_did}:2:{schema_name}:{schema_version}" - schema = await self.fetch_schema_by_id(fetch_schema_id) + schema = await self.fetch_schema_by_id(schema_id) if schema: fetched_attrs = schema["attrNames"].copy() - fetched_attrs.sort() - cmp_attrs = list(attribute_names) - cmp_attrs.sort() - if fetched_attrs != cmp_attrs: + if set(fetched_attrs) != set(attribute_names): raise LedgerTransactionError( "Schema already exists on ledger, but attributes do not match: " - + f"{schema_name}:{schema_version} {fetched_attrs} != {cmp_attrs}" + + f"{schema_id} {fetched_attrs} != {attribute_names}" ) - return fetch_schema_id, schema + return schema_id, schema - async def create_and_send_schema( + async def send_schema( self, - issuer: AnonCredsIssuer, - schema_name: str, - schema_version: str, - attribute_names: Sequence[str], + schema_id: str, + schema_def: dict, write_ledger: bool = True, - endorser_did: str = None, - ) -> Tuple[str, dict]: - """ - Send schema to ledger. - - Args: - issuer: The issuer instance to use for schema creation - schema_name: The schema name - schema_version: The schema version - attribute_names: A list of schema attributes - - """ - + endorser_did: Optional[str] = None, + ) -> int: + """Send an already created schema to the ledger.""" public_info = await self.get_wallet_public_did() if not public_info: raise BadLedgerRequestError("Cannot publish schema without a public DID") schema_info = await self.check_existing_schema( - public_info.did, schema_name, schema_version, attribute_names + schema_id, schema_def["attrNames"] ) + if schema_info: - LOGGER.warning("Schema already exists on ledger. Returning details.") - schema_id, schema_def = schema_info - else: - if await self.is_ledger_read_only(): - raise LedgerError( - "Error cannot write schema when ledger is in read only mode" - ) + raise LedgerObjectAlreadyExistsError( + "Schema already exists on ledger", schema_info + ) - try: - schema_id, schema_json = await issuer.create_schema( - public_info.did, - schema_name, - schema_version, - attribute_names, - ) - except AnonCredsIssuerError as err: - raise LedgerError(err.message) from err - schema_def = json.loads(schema_json) + if await self.is_ledger_read_only(): + raise LedgerError( + "Error cannot write schema when ledger is in read only mode" + ) - schema_req = await self._create_schema_request( - public_info, - schema_json, + schema_req = await self._create_schema_request( + public_info, + json.dumps(schema_def), + write_ledger=write_ledger, + endorser_did=endorser_did, + ) + + try: + resp = await self.txn_submit( + schema_req, + sign=True, + sign_did=public_info, write_ledger=write_ledger, - endorser_did=endorser_did, ) + # TODO Clean this up + # if not write_ledger: + # return schema_id, {"signed_txn": resp} + try: - resp = await self.txn_submit( - schema_req, - sign=True, - sign_did=public_info, - write_ledger=write_ledger, + # parse sequence number out of response + seq_no = json.loads(resp)["result"]["txnMetadata"]["seqNo"] + return seq_no + except KeyError as err: + raise LedgerError( + "Failed to parse schema sequence number from ledger response" + ) from err + except LedgerTransactionError as e: + # Identify possible duplicate schema errors on indy-node < 1.9 and > 1.9 + if ( + "can have one and only one SCHEMA with name" in e.message + or "UnauthorizedClientRequest" in e.message + ): + # handle potential race condition if multiple agents are publishing + # the same schema simultaneously + schema_info = await self.check_existing_schema( + schema_id, schema_def["attrNames"] ) - - if not write_ledger: - return schema_id, {"signed_txn": resp} - - try: - # parse sequence number out of response - seq_no = json.loads(resp)["result"]["txnMetadata"]["seqNo"] - schema_def["seqNo"] = seq_no - except KeyError as err: - raise LedgerError( - "Failed to parse schema sequence number from ledger response" - ) from err - except LedgerTransactionError as e: - # Identify possible duplicate schema errors on indy-node < 1.9 and > 1.9 - if ( - "can have one and only one SCHEMA with name" in e.message - or "UnauthorizedClientRequest" in e.message - ): - # handle potential race condition if multiple agents are publishing - # the same schema simultaneously - schema_info = await self.check_existing_schema( - public_info.did, schema_name, schema_version, attribute_names + if schema_info: + LOGGER.warning( + "Schema already exists on ledger. Returning details." + " Error: %s", + e, + ) + raise LedgerObjectAlreadyExistsError( + f"Schema already exists on ledger (Error: {e})", schema_info ) - if schema_info: - LOGGER.warning( - "Schema already exists on ledger. Returning details." - " Error: %s", - e, - ) - schema_id, schema_def = schema_info else: raise - - return schema_id, schema_def + else: + raise @abstractmethod async def _create_schema_request( diff --git a/aries_cloudagent/ledger/error.py b/aries_cloudagent/ledger/error.py index 700ed89dc1..49b87b2b16 100644 --- a/aries_cloudagent/ledger/error.py +++ b/aries_cloudagent/ledger/error.py @@ -1,5 +1,6 @@ """Ledger related errors.""" +from typing import Any, Generic, Optional, TypeVar from ..core.error import BaseError @@ -21,3 +22,21 @@ class ClosedPoolError(LedgerError): class LedgerTransactionError(LedgerError): """The ledger rejected the transaction.""" + + +T = TypeVar("T") + + +class LedgerObjectAlreadyExistsError(LedgerError, Generic[T]): + """Raised when a ledger object already existed.""" + + def __init__(self, message: Optional[str] = None, obj: T = None, *args, **kwargs): + super().__init__(message, obj, *args, **kwargs) + self.obj = obj + + @property + def message(self): + if self.args[0] and self.args[1]: + return f"{self.args[0]}: {self.args[1]}" + else: + return super().message From 75df7833afd0273cd36ac741ae719f1858f1ca76 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Sat, 25 Mar 2023 13:11:11 -0400 Subject: [PATCH 073/150] fix: typos and imports Signed-off-by: Daniel Bluhm --- .../anoncreds/anoncreds/anoncreds_registry.py | 8 ++------ .../anoncreds/anoncreds/base_registry.py | 18 ++---------------- aries_cloudagent/anoncreds/anoncreds/issuer.py | 8 +++++--- .../anoncreds/models/anoncreds_schema.py | 7 ++++++- docker/Dockerfile.run | 17 +++++++++++++---- 5 files changed, 28 insertions(+), 30 deletions(-) diff --git a/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py b/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py index 4d6ffd67dc..f377f29295 100644 --- a/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py @@ -10,12 +10,10 @@ AnonCredsRegistryGetRevocationList, AnonCredsRegistryGetRevocationRegistryDefinition, ) -from ..models.anoncreds_schema import AnonCredsRegistryGetSchema, SchemaResult +from ..models.anoncreds_schema import GetSchemaResult, SchemaResult from .base_registry import ( - AnonCredsObjectNotFound, AnonCredsRegistrationError, AnonCredsResolutionError, - BaseAnonCredsError, BaseAnonCredsHandler, BaseAnonCredsRegistrar, BaseAnonCredsResolver, @@ -69,9 +67,7 @@ async def _registrar_for_identifier(self, identifier: str): async def setup(self, context: InjectionContext): """Setup method.""" - async def get_schema( - self, profile: Profile, schema_id: str - ) -> AnonCredsRegistryGetSchema: + async def get_schema(self, profile: Profile, schema_id: str) -> GetSchemaResult: """Get a schema from the registry.""" resolver = await self._resolver_for_identifier(schema_id) return await resolver.get_schema(profile, schema_id) diff --git a/aries_cloudagent/anoncreds/anoncreds/base_registry.py b/aries_cloudagent/anoncreds/anoncreds/base_registry.py index 68682b2d1d..196b566397 100644 --- a/aries_cloudagent/anoncreds/anoncreds/base_registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/base_registry.py @@ -8,14 +8,12 @@ from ...core.profile import Profile from ..models.anoncreds_cred_def import ( AnonCredsRegistryGetCredentialDefinition, - AnonCredsRegistryGetCredentialDefinitions, AnonCredsRegistryGetRevocationList, AnonCredsRegistryGetRevocationRegistryDefinition, AnonCredsRegistryGetRevocationRegistryDefinitions, ) from ..models.anoncreds_schema import ( - AnonCredsRegistryGetSchema, - AnonCredsRegistryGetSchemas, + GetSchemaResult, SchemaResult, ) @@ -85,27 +83,15 @@ async def setup(self, context: InjectionContext): class BaseAnonCredsResolver(BaseAnonCredsHandler): @abstractmethod - async def get_schema(self, schema_id: str) -> AnonCredsRegistryGetSchema: + async def get_schema(self, schema_id: str) -> GetSchemaResult: """Get a schema from the registry.""" - @abstractmethod - async def get_schemas( - self, profile: Profile, filter: dict - ) -> AnonCredsRegistryGetSchemas: - """Get a schema ids from the registry.""" - @abstractmethod async def get_credential_definition( self, credential_definition_id: str ) -> AnonCredsRegistryGetCredentialDefinition: """Get a credential definition from the registry.""" - @abstractmethod - async def get_credential_definitions( - self, profile: Profile, filter: dict - ) -> AnonCredsRegistryGetCredentialDefinitions: - """Get a credential definition ids from the registry.""" - @abstractmethod async def get_revocation_registry_definition( self, revocation_registry_id: str diff --git a/aries_cloudagent/anoncreds/anoncreds/issuer.py b/aries_cloudagent/anoncreds/anoncreds/issuer.py index a61ab2cb17..09d6a750f5 100644 --- a/aries_cloudagent/anoncreds/anoncreds/issuer.py +++ b/aries_cloudagent/anoncreds/anoncreds/issuer.py @@ -1,6 +1,7 @@ """Indy issuer implementation.""" import asyncio +import json import logging import time @@ -99,7 +100,8 @@ async def create_schema( ) if schemas: raise AnonCredsSchemaAlreadyExists( - f"Schema with {schema_name}: {schema_version} already exists for {issuer_id}" + f"Schema with {schema_name}: {schema_version} " + f"already exists for {issuer_id}" ) try: @@ -174,8 +176,8 @@ async def get_created_schemas( if value is not None }, ) - # schema is an Entry; Entry.name was stored as the schema's ID - return [schema.name for schema in schemas] + # entry.name was stored as the schema's ID + return [entry.name for entry in schemas] async def credential_definition_in_wallet( self, credential_definition_id: str diff --git a/aries_cloudagent/anoncreds/models/anoncreds_schema.py b/aries_cloudagent/anoncreds/models/anoncreds_schema.py index 8c4db74388..f8330cbd76 100644 --- a/aries_cloudagent/anoncreds/models/anoncreds_schema.py +++ b/aries_cloudagent/anoncreds/models/anoncreds_schema.py @@ -1,6 +1,6 @@ """Anoncreds Schema OpenAPI validators""" -from typing import Any, Dict, List, Mapping, Optional, Union +from typing import Any, Dict, List, Optional from marshmallow import EXCLUDE, fields from marshmallow.validate import OneOf @@ -153,6 +153,11 @@ def __init__(self, state: str, schema_id: str, schema: AnonCredsSchema, **kwargs class SchemaStateSchema(BaseModelSchema): """Parameters and validators for schema state.""" + class Meta: + """SchemaStateSchema metadata.""" + + model_class = SchemaState + state = fields.Str( validate=OneOf( [ diff --git a/docker/Dockerfile.run b/docker/Dockerfile.run index fe1164f647..8f55992959 100644 --- a/docker/Dockerfile.run +++ b/docker/Dockerfile.run @@ -1,6 +1,13 @@ -FROM bcgovimages/von-image:py36-1.15-1 +ARG python_version=3.9.16 +FROM python:${python_version}-slim-buster -ENV ENABLE_PTVSD 0 +RUN apt-get update -y && \ + apt-get install -y --no-install-recommends \ + libsodium23 git curl && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +WORKDIR /usr/src/app ADD requirements*.txt ./ @@ -8,7 +15,9 @@ RUN pip3 install --no-cache-dir \ -r requirements.txt \ -r requirements.askar.txt \ -r requirements.bbs.txt \ - -r requirements.dev.txt + -r requirements.dev.txt \ + -r requirements.anoncreds.txt +RUN curl -sL https://github.com/Indicio-tech/anoncreds-rs/releases/download/v0.1.0-dev.9/library-linux-x86_64.tar.gz | tar -xz -C /usr/local/lib/python3.9/site-packages/anoncreds/ RUN mkdir aries_cloudagent && touch aries_cloudagent/__init__.py ADD aries_cloudagent/version.py aries_cloudagent/version.py @@ -18,7 +27,7 @@ ADD setup.py ./ RUN pip3 install --no-cache-dir -e . -RUN mkdir logs && chown -R indy:indy logs && chmod -R ug+rw logs +RUN mkdir logs && chmod -R ug+rw logs ADD aries_cloudagent ./aries_cloudagent ENTRYPOINT ["/bin/bash", "-c", "aca-py \"$@\"", "--"] From 10d7972c5790751262cf2ad614ed128832a142af Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Sat, 25 Mar 2023 13:26:16 -0400 Subject: [PATCH 074/150] feat: clean up models Signed-off-by: Daniel Bluhm --- .../anoncreds/anoncreds/routes.py | 48 ++++++++++-- .../anoncreds/models/anoncreds_schema.py | 76 +------------------ aries_cloudagent/anoncreds/models/schema.py | 27 ------- aries_cloudagent/messaging/schemas/routes.py | 43 +++++++---- 4 files changed, 74 insertions(+), 120 deletions(-) delete mode 100644 aries_cloudagent/anoncreds/models/schema.py diff --git a/aries_cloudagent/anoncreds/anoncreds/routes.py b/aries_cloudagent/anoncreds/anoncreds/routes.py index d78145b055..2021a35468 100644 --- a/aries_cloudagent/anoncreds/anoncreds/routes.py +++ b/aries_cloudagent/anoncreds/anoncreds/routes.py @@ -18,12 +18,9 @@ AnonCredsRegistryGetCredentialDefinitionSchema, ) from ..models.anoncreds_schema import ( - AnonCredsRegistryGetSchemasSchema, - AnonCredsSchema, + AnonCredsSchemaSchema, SchemaResultSchema, - SchemaPostRequestSchema, GetSchemaResultSchema, - SchemasQueryStringSchema, ) from aries_cloudagent.anoncreds.models.anoncreds_valid import ( ANONCREDS_SCHEMA_ID, @@ -150,6 +147,23 @@ class GetCredDefsResponseSchema(OpenAPISchema): credential_definition_id = fields.Str() +class SchemaPostOptionSchema(OpenAPISchema): + """Parameters and validators for schema options.""" + + endorser_connection_id = fields.UUID( + description="Connection identifier (optional) (this is an example)", + required=False, + example=UUIDFour.EXAMPLE, + ) + + +class SchemaPostRequestSchema(OpenAPISchema): + """Parameters and validators for query string in create schema.""" + + schema = fields.Nested(AnonCredsSchemaSchema()) + options = fields.Nested(SchemaPostOptionSchema()) + + @docs(tags=["anoncreds"], summary="") @request_schema(SchemaPostRequestSchema()) @response_schema(SchemaResultSchema(), 200, description="") @@ -220,9 +234,33 @@ async def schema_get(request: web.BaseRequest): return web.json_response(result.serialize()) +class SchemasQueryStringSchema(OpenAPISchema): + """Parameters and validators for query string in schemas list query.""" + + schema_name = fields.Str( + description="Schema name", + example="example-schema", + ) + schema_version = fields.Str(description="Schema version") + schema_issuer_id = fields.Str( + description="Issuer Identifier of the credential definition or schema", + ) + + +class GetSchemasResponseSchema(OpenAPISchema): + """Parameters and validators for schema list all response.""" + + schema_ids = fields.List( + fields.Str( + data_key="schemaIds", + description="Schema identifier", + ) + ) + + @docs(tags=["anoncreds"], summary="") @querystring_schema(SchemasQueryStringSchema()) -@response_schema(AnonCredsRegistryGetSchemasSchema(), 200, description="") +@response_schema(GetSchemasResponseSchema(), 200, description="") async def schemas_get(request: web.BaseRequest): """Request handler for getting all schemas. diff --git a/aries_cloudagent/anoncreds/models/anoncreds_schema.py b/aries_cloudagent/anoncreds/models/anoncreds_schema.py index f8330cbd76..473ffd04e3 100644 --- a/aries_cloudagent/anoncreds/models/anoncreds_schema.py +++ b/aries_cloudagent/anoncreds/models/anoncreds_schema.py @@ -5,13 +5,7 @@ from marshmallow import EXCLUDE, fields from marshmallow.validate import OneOf -from aries_cloudagent.anoncreds.models.anoncreds_valid import ( - ANONCREDS_SCHEMA_ID, -) -from aries_cloudagent.messaging.models.base import BaseModel, BaseModelSchema - -from ...messaging.models.openapi import OpenAPISchema -from ...messaging.valid import UUIDFour +from ...messaging.models.base import BaseModel, BaseModelSchema class AnonCredsSchema(BaseModel): @@ -55,7 +49,6 @@ class Meta: ) name = fields.Str( description="Schema name", - example=ANONCREDS_SCHEMA_ID["example"].split(":")[2], ) version = fields.Str(description="Schema version") @@ -93,42 +86,11 @@ class Meta: unknown = EXCLUDE schema_ = fields.Nested(AnonCredsSchemaSchema(), data_key="schema") - schema_id = fields.Str( - data_key="schemaId", description="Schema identifier", **ANONCREDS_SCHEMA_ID - ) + schema_id = fields.Str(data_key="schemaId", description="Schema identifier") resolution_metadata = fields.Dict() schema_metadata = fields.Dict() -class AnonCredsRegistryGetSchemas(BaseModel): - """Result of retrieving created schemas.""" - - class Meta: - """IndyCredInfo metadata.""" - - schema_class = "AnonCredsRegistryGetSchemasSchema" - - def __init__(self, schema_ids: list, **kwargs): - super().__init__(**kwargs) - self.schema_ids = schema_ids - - -class AnonCredsRegistryGetSchemasSchema(BaseModelSchema): - """Parameters and validators for schema list all response.""" - - class Meta: - """AnonCredsRegistryGetSchemasSchema metadata.""" - - model_class = AnonCredsRegistryGetSchemas - unknown = EXCLUDE - - schema_ids = fields.List( - fields.Str( - data_key="schemaIds", description="Schema identifier", **ANONCREDS_SCHEMA_ID - ) - ) - - class SchemaState(BaseModel): """Model representing the state of a schema after beginning registration.""" @@ -168,9 +130,7 @@ class Meta: ] ) ) - schema_id = fields.Str( - data_key="schemaId", description="Schema identifier", **ANONCREDS_SCHEMA_ID - ) + schema_id = fields.Str(data_key="schemaId", description="Schema identifier") schema_ = fields.Nested(AnonCredsSchemaSchema(), data_key="schema") @@ -205,33 +165,3 @@ class SchemaResultSchema(BaseModelSchema): registration_metadata = fields.Dict() # For indy, schema_metadata will contain the seqNo schema_metadata = fields.Dict() - - -class SchemasQueryStringSchema(OpenAPISchema): - """Parameters and validators for query string in schemas list query.""" - - schema_name = fields.Str( - description="Schema name", - example="example-schema", - ) - schema_version = fields.Str(description="Schema version") - schema_issuer_id = fields.Str( - description="Issuer Identifier of the credential definition or schema", - ) - - -class SchemaPostOptionSchema(OpenAPISchema): - """Parameters and validators for schema options.""" - - endorser_connection_id = fields.UUID( - description="Connection identifier (optional) (this is an example)", - required=False, - example=UUIDFour.EXAMPLE, - ) - - -class SchemaPostRequestSchema(OpenAPISchema): - """Parameters and validators for query string in create schema.""" - - schema = fields.Nested(AnonCredsSchemaSchema()) - options = fields.Nested(SchemaPostOptionSchema()) diff --git a/aries_cloudagent/anoncreds/models/schema.py b/aries_cloudagent/anoncreds/models/schema.py deleted file mode 100644 index 4a48aaf1b3..0000000000 --- a/aries_cloudagent/anoncreds/models/schema.py +++ /dev/null @@ -1,27 +0,0 @@ -"""Schema artifacts.""" - -from marshmallow import fields - -from ...messaging.models.openapi import OpenAPISchema -from ...messaging.valid import INDY_SCHEMA_ID, INDY_VERSION, NATURAL_NUM - - -class SchemaSchema(OpenAPISchema): - """Marshmallow schema for indy schema.""" - - ver = fields.Str(description="Node protocol version", **INDY_VERSION) - ident = fields.Str(data_key="id", description="Schema identifier", **INDY_SCHEMA_ID) - name = fields.Str( - description="Schema name", - example=INDY_SCHEMA_ID["example"].split(":")[2], - ) - version = fields.Str(description="Schema version", **INDY_VERSION) - attr_names = fields.List( - fields.Str( - description="Attribute name", - example="score", - ), - description="Schema attribute names", - data_key="attrNames", - ) - seqNo = fields.Int(description="Schema sequence number", strict=True, **NATURAL_NUM) diff --git a/aries_cloudagent/messaging/schemas/routes.py b/aries_cloudagent/messaging/schemas/routes.py index 61c8e9855a..957b3f0a25 100644 --- a/aries_cloudagent/messaging/schemas/routes.py +++ b/aries_cloudagent/messaging/schemas/routes.py @@ -1,10 +1,9 @@ """Credential schema admin routes.""" +from asyncio import shield import json from time import time -from asyncio import shield - from aiohttp import web from aiohttp_apispec import ( docs, @@ -13,15 +12,14 @@ request_schema, response_schema, ) - from marshmallow import fields from marshmallow.validate import Regexp from ...admin.request_context import AdminRequestContext +from ...anoncreds.issuer import AnonCredsIssuer, AnonCredsIssuerError +from ...connections.models.conn_record import ConnRecord from ...core.event_bus import Event, EventBus from ...core.profile import Profile -from ...anoncreds.issuer import AnonCredsIssuer, AnonCredsIssuerError -from ...anoncreds.models.schema import SchemaSchema from ...ledger.base import BaseLedger from ...ledger.error import LedgerError from ...ledger.multiple_ledger.ledger_requests_executor import ( @@ -37,28 +35,43 @@ TransactionRecordSchema, ) from ...protocols.endorse_transaction.v1_0.util import ( - is_author_role, get_endorser_connection_id, + is_author_role, ) from ...storage.base import BaseStorage, StorageRecord from ...storage.error import StorageError - +from ...storage.error import StorageNotFoundError +from ..models.base import BaseModelError from ..models.openapi import OpenAPISchema -from ..valid import B58, INDY_SCHEMA_ID, INDY_VERSION - +from ..valid import B58, INDY_SCHEMA_ID, INDY_VERSION, NATURAL_NUM, UUIDFour from .util import ( - SchemaQueryStringSchema, + EVENT_LISTENER_PATTERN, SCHEMA_SENT_RECORD_TYPE, SCHEMA_TAGS, - EVENT_LISTENER_PATTERN, + SchemaQueryStringSchema, notify_schema_event, ) -from ..valid import UUIDFour -from ...connections.models.conn_record import ConnRecord -from ...storage.error import StorageNotFoundError -from ..models.base import BaseModelError +class SchemaSchema(OpenAPISchema): + """Marshmallow schema for indy schema.""" + + ver = fields.Str(description="Node protocol version", **INDY_VERSION) + ident = fields.Str(data_key="id", description="Schema identifier", **INDY_SCHEMA_ID) + name = fields.Str( + description="Schema name", + example=INDY_SCHEMA_ID["example"].split(":")[2], + ) + version = fields.Str(description="Schema version", **INDY_VERSION) + attr_names = fields.List( + fields.Str( + description="Attribute name", + example="score", + ), + description="Schema attribute names", + data_key="attrNames", + ) + seqNo = fields.Int(description="Schema sequence number", strict=True, **NATURAL_NUM) class SchemaSendRequestSchema(OpenAPISchema): From 6f86a443c35c1142444c5eb4dd9422e59fa69705 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Sat, 25 Mar 2023 13:38:33 -0400 Subject: [PATCH 075/150] fix: schemas and flake8 errors Trimmed back the did:indy impl; we'll flesh this back out after legacy indy Signed-off-by: Daniel Bluhm --- .../default/did_indy_registry/registry.py | 236 +----------------- .../default/did_web_registry/registry.py | 4 +- .../default/legacy_indy_registry/registry.py | 62 +---- .../anoncreds/models/anoncreds_schema.py | 5 + 4 files changed, 12 insertions(+), 295 deletions(-) diff --git a/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py index 23aa4e4ed0..6a6e04c7b9 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py @@ -1,35 +1,16 @@ """DID Indy Registry""" import logging -from asyncio import shield import re from typing import Pattern from .....config.injection_context import InjectionContext from .....core.profile import Profile -from .....ledger.base import BaseLedger -from .....ledger.error import LedgerError -from .....ledger.multiple_ledger.ledger_requests_executor import ( - GET_CRED_DEF, - GET_SCHEMA, - IndyLedgerRequestsExecutor, -) -from .....messaging.credential_definitions.util import ( - CRED_DEF_SENT_RECORD_TYPE, - notify_cred_def_event, -) -from .....messaging.schemas.util import SCHEMA_SENT_RECORD_TYPE, notify_schema_event -from .....multitenant.base import BaseMultitenantManager -from .....revocation.error import RevocationError -from .....revocation.indy import IndyRevocation -from .....storage.base import BaseStorage -from .....storage.error import StorageNotFoundError -from ....issuer import AnonCredsIssuer, AnonCredsIssuerError from ....models.anoncreds_cred_def import ( AnonCredsRegistryGetCredentialDefinition, AnonCredsRegistryGetRevocationList, AnonCredsRegistryGetRevocationRegistryDefinition, ) -from ....models.anoncreds_schema import AnonCredsRegistryGetSchema +from ....models.anoncreds_schema import GetSchemaResult from ...base_registry import BaseAnonCredsRegistrar, BaseAnonCredsResolver LOGGER = logging.getLogger(__name__) @@ -50,35 +31,8 @@ async def setup(self, context: InjectionContext): """Setup.""" print("Successfully registered DIDIndyRegistry") - async def get_schema( - self, profile: Profile, schema_id - ) -> AnonCredsRegistryGetSchema: + async def get_schema(self, profile: Profile, schema_id) -> GetSchemaResult: """Get a schema from the registry.""" - multitenant_mgr = profile.inject_or(BaseMultitenantManager) - if multitenant_mgr: - ledger_exec_inst = IndyLedgerRequestsExecutor(profile) - else: - ledger_exec_inst = profile.inject(IndyLedgerRequestsExecutor) - ledger_id, ledger = await ledger_exec_inst.get_ledger_for_identifier( - schema_id, - txn_record_type=GET_SCHEMA, - ) - if not ledger: - reason = "No ledger available" - if not profile.settings.get_value("wallet.type"): - reason += ": missing wallet-type?" - raise # TODO: create AnonCreds error - - async with ledger: - try: - schema = await ledger.get_schema(schema_id) - # TODO: use schema to create AnonCredsSchema and AnonCredsRegistryGetSchema objects - # ledger_id goes in resolution_metadata - except LedgerError as err: - raise # TODO: create AnonCreds error - return schema - - # TODO: job_id? async def get_schemas(self, profile: Profile, filter: str): """Get schema ids filtered by filter""" @@ -91,107 +45,12 @@ async def register_schema( schema, ): """Register a schema on the registry.""" - # TODO: need issuer_id to - # issuer_id needs to sign the transaction too - - # Check that schema doesn't already exist - tag_query = {"schema_name": schema.name, "schema_version": schema.version} - async with profile.session() as session: - storage = session.inject(BaseStorage) - found = await storage.find_all_records( - type_filter=SCHEMA_SENT_RECORD_TYPE, - tag_query=tag_query, - ) - if 0 < len(found): - raise # Anoncreds error: f"Schema {schema_name} {schema_version} already exists" - - # Assume endorser role on the network - # No option for 3rd-party endorser - - ledger = profile.inject_or(BaseLedger) - if not ledger: - reason = "No ledger available" - if not profile.settings.get_value("wallet.type"): - reason += ": missing wallet-type?" - raise # Anoncreds error | web.HTTPForbidden(reason=reason) - - issuer = profile.inject(AnonCredsIssuer) - async with ledger: - try: - # if create_transaction_for_endorser, then the returned "schema_def" - # is actually the signed transaction - schema_id, schema_def = await shield( - ledger.create_and_send_schema( - issuer, - schema.name, - schema.version, - schema.attr_names, - write_ledger=True, # TODO: check - endorser_did=schema.issuer_id, - ) - ) - except (AnonCredsIssuerError, LedgerError) as err: - raise # Anoncreds error | web.HTTPBadRequest(reason=err.roll_up) from err - - # TODO: use AnonCredsSchema object? - meta_data = { - "context": { - "schema_id": schema_id, - "schema_name": schema.name, - "schema_version": schema.version, - "attributes": schema.attr_names, - }, - "processing": {}, - } - - # Notify event - await notify_schema_event(profile, schema_id, meta_data) - - return { - "job_id": None, - "schema_state": { - "state": "finished", - "schema_id": schema_id, - "schema": { - "attrNames": schema_def["attrNames"], - "name": schema_def["name"], - "version": schema_def["ver"], - "issuerId": schema.issuer_id, - }, - }, - "registration_metadata": {}, - # For indy, schema_metadata will contain the seqNo - "schema_metadata": {"seqNo": schema_def["seqNo"]}, - } async def get_credential_definition( self, profile: Profile, cred_def_id: str ) -> AnonCredsRegistryGetCredentialDefinition: """Get a credential definition from the registry.""" - async with profile.session() as session: - multitenant_mgr = session.inject_or(BaseMultitenantManager) - if multitenant_mgr: - ledger_exec_inst = IndyLedgerRequestsExecutor(profile) - else: - ledger_exec_inst = session.inject(IndyLedgerRequestsExecutor) - ledger_id, ledger = await ledger_exec_inst.get_ledger_for_identifier( - cred_def_id, - txn_record_type=GET_CRED_DEF, - ) - if not ledger: - reason = "No ledger available" - if not profile.settings.get_value("wallet.type"): - reason += ": missing wallet-type?" - raise # Anoncreds error | web.HTTPForbidden(reason=reason) - - async with ledger: - cred_def = await ledger.get_credential_definition(cred_def_id) - - return cred_def - - # job_id - async def get_credential_definitions(self, profile: Profile, filter: str): """Get credential definition ids filtered by filter""" @@ -207,87 +66,11 @@ async def register_credential_definition( ): """Register a credential definition on the registry.""" - tag_query = {"schema_id": schema_id} - async with profile.session() as session: - storage = session.inject(BaseStorage) - found = await storage.find_all_records( - type_filter=CRED_DEF_SENT_RECORD_TYPE, - tag_query=tag_query, - ) - if 0 < len(found): - # need to check the 'tag' value - for record in found: - cred_def_id = record.value - cred_def_id_parts = cred_def_id.split(":") - if tag == cred_def_id_parts[4]: - raise # Anoncreds error: web.HTTPBadRequest( - # reason=f"Cred def for {schema_id} {tag} already exists" - # ) - - ledger = profile.inject_or(BaseLedger) - if not ledger: - reason = "No ledger available" - if not profile.settings.get_value("wallet.type"): - reason += ": missing wallet-type?" - raise # Anoncreds error web.HTTPForbidden(reason=reason) - - issuer = profile.inject(AnonCredsIssuer) - try: # even if in wallet, send it and raise if erroneously so - async with ledger: - (cred_def_id, cred_def, novel) = await shield( - ledger.create_and_send_credential_definition( - issuer, - schema_id, - signature_type=None, - tag=tag, - support_revocation=support_revocation, - write_ledger=True, # TODO: check - endorser_did=issuer_id, - ) - ) - - except (AnonCredsIssuerError, LedgerError) as e: - raise # Anoncreds error web.HTTPBadRequest(reason=e.message) from e - - issuer_did = cred_def_id.split(":")[0] - meta_data = { - "context": { - "schema_id": schema_id, - "cred_def_id": cred_def_id, - "issuer_did": issuer_did, - "support_revocation": support_revocation, - "novel": novel, - "tag": tag, - "rev_reg_size": rev_reg_size, - }, - "processing": { - "create_pending_rev_reg": True, - }, - } - - # Notify event - meta_data["processing"]["auto_create_rev_reg"] = True - await notify_cred_def_event(profile, cred_def_id, meta_data) - - return { # TODO: check - "sent": {"credential_definition_id": cred_def_id}, - "credential_definition_id": cred_def_id, - } - async def get_revocation_registry_definition( self, profile: Profile, rev_reg_id: str ) -> AnonCredsRegistryGetRevocationRegistryDefinition: """Get a revocation registry definition from the registry.""" - try: - revoc = IndyRevocation(profile) - rev_reg = await revoc.get_issuer_rev_reg_record(rev_reg_id) - except StorageNotFoundError as err: - raise # Anoncreds error web.HTTPNotFound(reason=err.roll_up) from err - - return rev_reg.serialize - # use AnonCredsRevocationRegistryDefinition object - # TODO: determine keyword arguments async def register_revocation_registry_definition( self, @@ -297,21 +80,6 @@ async def register_revocation_registry_definition( ): """Register a revocation registry definition on the registry.""" - try: - revoc = IndyRevocation(profile) - rev_reg = await revoc.get_issuer_rev_reg_record(rev_reg_id) - - rev_reg_resp = await rev_reg.send_def( - profile, - write_ledger=True, - endorser_did=issuer_id, - ) - LOGGER.debug("published rev reg definition: %s", rev_reg_id) - except StorageNotFoundError as err: - raise # Anoncreds error web.HTTPNotFound(reason=err.roll_up) from err - except RevocationError as err: - raise # Anoncreds error web.HTTPBadRequest(reason=err.roll_up) from err - async def get_revocation_registry_definitions(self, profile: Profile, filter: str): """Get credential definition ids filtered by filter""" diff --git a/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py index 9fbc438593..39b2e6a666 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py @@ -9,7 +9,7 @@ AnonCredsRegistryGetRevocationList, AnonCredsRegistryGetRevocationRegistryDefinition, ) -from ....models.anoncreds_schema import AnonCredsRegistryGetSchema +from ....models.anoncreds_schema import GetSchemaResult from ...base_registry import BaseAnonCredsRegistrar, BaseAnonCredsResolver @@ -28,7 +28,7 @@ async def setup(self, context: InjectionContext): """Setup.""" print("Successfully registered DIDWebRegistry") - async def get_schema(self, profile, schema_id: str) -> AnonCredsRegistryGetSchema: + async def get_schema(self, profile, schema_id: str) -> GetSchemaResult: """Get a schema from the registry.""" async def get_schemas(self, profile: Profile, filter: str): diff --git a/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py index 9bc5772d63..7a99a89fe3 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py @@ -16,11 +16,9 @@ IndyLedgerRequestsExecutor, ) from .....messaging.credential_definitions.util import notify_cred_def_event -from .....messaging.schemas.util import SCHEMA_SENT_RECORD_TYPE, notify_schema_event from .....multitenant.base import BaseMultitenantManager from .....revocation.error import RevocationError from .....revocation.indy import IndyRevocation -from .....storage.base import BaseStorage from .....storage.error import StorageNotFoundError from ....issuer import AnonCredsIssuer, AnonCredsIssuerError from ....models.anoncreds_cred_def import ( @@ -31,13 +29,12 @@ AnonCredsRegistryGetRevocationRegistryDefinition, ) from ....models.anoncreds_schema import ( - AnonCredsRegistryGetSchema, + GetSchemaResult, AnonCredsSchema, SchemaResult, SchemaState, ) from ...base_registry import ( - AnonCredsObjectAlreadyExists, AnonCredsRegistrationError, AnonCredsResolutionError, AnonCredsSchemaAlreadyExists, @@ -63,9 +60,7 @@ async def setup(self, context: InjectionContext): """Setup.""" print("Successfully registered LegacyIndyRegistry") - async def get_schema( - self, profile: Profile, schema_id: str - ) -> AnonCredsRegistryGetSchema: + async def get_schema(self, profile: Profile, schema_id: str) -> GetSchemaResult: """Get a schema from the registry.""" multitenant_mgr = profile.inject_or(BaseMultitenantManager) @@ -92,7 +87,7 @@ async def get_schema( name=schema["name"], version=schema["ver"], ) - anoncreds_registry_get_schema = AnonCredsRegistryGetSchema( + anoncreds_registry_get_schema = GetSchemaResult( schema=anonscreds_schema, schema_id=schema["id"], resolution_metadata={"ledger_id": ledger_id}, @@ -102,27 +97,6 @@ async def get_schema( raise AnonCredsResolutionError(err) return anoncreds_registry_get_schema - async def get_schemas(self, profile: Profile, filter: dict): - """Get schema ids filtered by filter""" - - tag_query = {} - schema_issuer_did = filter.get("issuerId") - schema_name = filter.get("name") - schema_version = filter.get("version") - - if schema_issuer_did: - tag_query["schema_issuer_did"] = schema_issuer_did - if schema_name: - tag_query["schema_name"] = schema_name - if schema_version: - tag_query["schema_version"] = schema_version - - session = await profile.session() - storage = session.inject(BaseStorage) - return await storage.find_all_records( - type_filter=SCHEMA_SENT_RECORD_TYPE, tag_query=tag_query - ) - async def register_schema( self, profile: Profile, @@ -208,36 +182,6 @@ async def get_credential_definition( ) return anoncreds_registry_get_credential_definition - async def get_credential_definitions(self, profile: Profile, filter: str): - """Get credential definition ids filtered by filter""" - - tag_query = {} - cred_def_id = filter.get("cred_def_id") - issuer_id = filter.get("issuer_id") - schema_id = filter.get("schema_id") - schema_issuer_id = filter.get("schema_issuer_id") - schema_name = filter.get("schema_name") - schema_version = filter.get("schema_version") - - if cred_def_id: - tag_query["cred_def_id"] = cred_def_id - if issuer_id: - tag_query["issuer_id"] = issuer_id - if schema_id: - tag_query["schema_id"] = schema_id - if schema_issuer_id: - tag_query["schema_issuer_id"] = schema_issuer_id - if schema_name: - tag_query["schema_name"] = schema_name - if schema_version: - tag_query["schema_version"] = schema_version - - session = await profile.session() - storage = session.inject(BaseStorage) - return await storage.find_all_records( - type_filter=SCHEMA_SENT_RECORD_TYPE, tag_query=tag_query - ) - async def register_credential_definition( self, profile: Profile, diff --git a/aries_cloudagent/anoncreds/models/anoncreds_schema.py b/aries_cloudagent/anoncreds/models/anoncreds_schema.py index 473ffd04e3..367cc20b3d 100644 --- a/aries_cloudagent/anoncreds/models/anoncreds_schema.py +++ b/aries_cloudagent/anoncreds/models/anoncreds_schema.py @@ -160,6 +160,11 @@ def __init__( class SchemaResultSchema(BaseModelSchema): """Parameters and validators for schema state.""" + class Meta: + """SchemaResultSchema metadata.""" + + model_class = SchemaResult + job_id = fields.Str() schema_state = fields.Nested(SchemaStateSchema()) registration_metadata = fields.Dict() From b6c30732b9acef5ae7787e6c60911ea1746dcc9e Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Sat, 25 Mar 2023 14:32:42 -0400 Subject: [PATCH 076/150] fix: mismatched parameter expectations Signed-off-by: Daniel Bluhm --- .../default/legacy_indy_registry/registry.py | 17 +++++++++++++++-- aries_cloudagent/anoncreds/anoncreds/routes.py | 9 ++++++++- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py index 7a99a89fe3..338390f827 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py @@ -108,7 +108,7 @@ async def register_schema( ) -> SchemaResult: """Register a schema on the registry.""" - schema = Schema.create(issuer_id, name, version, attr_names) + schema = Schema.create(name, version, issuer_id, attr_names) schema_id = f"{issuer_id}:2:{name}:{version}" # Assume endorser role on the network, no option for 3rd-party endorser @@ -120,9 +120,22 @@ async def register_schema( reason += ": missing wallet-type?" raise AnonCredsRegistrationError(reason) + # Translate schema into format expected by Indy + LOGGER.debug("Registering schema: %s", schema_id) + anoncreds_schema = schema.to_dict() + indy_schema = { + "ver": "1.0", + "id": schema_id, + "name": anoncreds_schema["name"], + "version": anoncreds_schema["version"], + "attrNames": anoncreds_schema["attrNames"], + "seqNo": None, + } + LOGGER.debug("schema value: %s", indy_schema) + async with ledger: try: - seq_no = await shield(ledger.send_schema(schema_id, schema.to_dict())) + seq_no = await shield(ledger.send_schema(schema_id, indy_schema)) except LedgerObjectAlreadyExistsError as err: raise AnonCredsSchemaAlreadyExists(err.message, err.obj) except (AnonCredsIssuerError, LedgerError) as err: diff --git a/aries_cloudagent/anoncreds/anoncreds/routes.py b/aries_cloudagent/anoncreds/anoncreds/routes.py index 2021a35468..7bd29c8174 100644 --- a/aries_cloudagent/anoncreds/anoncreds/routes.py +++ b/aries_cloudagent/anoncreds/anoncreds/routes.py @@ -208,8 +208,15 @@ async def schemas_post(request: web.BaseRequest): options = request_data.get("option") schema_data = request_data.get("schema") + issuer_id = schema_data.get("issuerId") + attr_names = schema_data.get("attrNames") + name = schema_data.get("name") + version = schema_data.get("version") + issuer = context.inject(AnonCredsIssuer) - result = await issuer.create_schema(**schema_data, options=options) + result = await issuer.create_schema( + issuer_id, name, version, attr_names, options=options + ) return web.json_response(result.serialize()) From 8d28760f27cc8ee50740696efcfa48a02a107cd1 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Sat, 25 Mar 2023 15:55:23 -0400 Subject: [PATCH 077/150] fix: incorrect schema name in model, field names Signed-off-by: Daniel Bluhm --- .../default/legacy_indy_registry/registry.py | 10 ++++++---- aries_cloudagent/anoncreds/anoncreds/issuer.py | 4 ++-- .../anoncreds/models/anoncreds_schema.py | 14 ++++++++------ 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py index 338390f827..24e0b54655 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py @@ -72,6 +72,7 @@ async def get_schema(self, profile: Profile, schema_id: str) -> GetSchemaResult: schema_id, txn_record_type=GET_SCHEMA, ) + if not ledger: reason = "No ledger available" if not profile.settings.get_value("wallet.type"): @@ -87,15 +88,16 @@ async def get_schema(self, profile: Profile, schema_id: str) -> GetSchemaResult: name=schema["name"], version=schema["ver"], ) - anoncreds_registry_get_schema = GetSchemaResult( + result = GetSchemaResult( schema=anonscreds_schema, schema_id=schema["id"], resolution_metadata={"ledger_id": ledger_id}, schema_metadata={"seqNo": schema["seqNo"]}, ) except LedgerError as err: - raise AnonCredsResolutionError(err) - return anoncreds_registry_get_schema + raise AnonCredsResolutionError("Failed to retrieve schema") from err + + return result async def register_schema( self, @@ -146,7 +148,7 @@ async def register_schema( schema_state=SchemaState( state=SchemaState.STATE_FINISHED, schema_id=schema_id, - schema=AnonCredsSchema.deserialize(schema.to_dict()), + schema_def=AnonCredsSchema.deserialize(anoncreds_schema), ), registration_metadata={}, schema_metadata={"seqNo": seq_no}, diff --git a/aries_cloudagent/anoncreds/anoncreds/issuer.py b/aries_cloudagent/anoncreds/anoncreds/issuer.py index 09d6a750f5..ff9e767eb8 100644 --- a/aries_cloudagent/anoncreds/anoncreds/issuer.py +++ b/aries_cloudagent/anoncreds/anoncreds/issuer.py @@ -119,7 +119,7 @@ async def create_schema( if schema_result.schema_state.state == SchemaState.STATE_FINISHED: await self.store_schema( schema_result.schema_state.schema_id, - schema_result.schema_state.schema.serialize(), + schema_result.schema_state.schema_def.serialize(), ) return schema_result @@ -172,7 +172,7 @@ async def get_created_schemas( "name": name, "version": version, "issuer_id": issuer_id, - } + }.items() if value is not None }, ) diff --git a/aries_cloudagent/anoncreds/models/anoncreds_schema.py b/aries_cloudagent/anoncreds/models/anoncreds_schema.py index 367cc20b3d..d9c32c2a37 100644 --- a/aries_cloudagent/anoncreds/models/anoncreds_schema.py +++ b/aries_cloudagent/anoncreds/models/anoncreds_schema.py @@ -59,7 +59,7 @@ class GetSchemaResult(BaseModel): class Meta: """GetSchemaResult metadata.""" - schema_class = "AnonCredsRegistryGetSchemaSchema" + schema_class = "GetSchemaResultSchema" def __init__( self, @@ -80,7 +80,7 @@ class GetSchemaResultSchema(BaseModelSchema): """Parameters and validators for schema create query.""" class Meta: - """AnonCredsRegistryGetSchemaSchema metadata.""" + """GetSchemaResultSchema metadata.""" model_class = GetSchemaResult unknown = EXCLUDE @@ -104,12 +104,14 @@ class Meta: schema_class = "SchemaStateSchema" - def __init__(self, state: str, schema_id: str, schema: AnonCredsSchema, **kwargs): + def __init__( + self, state: str, schema_id: str, schema_def: AnonCredsSchema, **kwargs + ): """Initialize a new SchemaState.""" super().__init__(**kwargs) self.state = state self.schema_id = schema_id - self.schema = schema + self.schema_def = schema_def class SchemaStateSchema(BaseModelSchema): @@ -130,8 +132,8 @@ class Meta: ] ) ) - schema_id = fields.Str(data_key="schemaId", description="Schema identifier") - schema_ = fields.Nested(AnonCredsSchemaSchema(), data_key="schema") + schema_id = fields.Str(description="Schema identifier") + schema_def = fields.Nested(AnonCredsSchemaSchema(), data_key="schema") class SchemaResult(BaseModel): From 833bc7bcdc8e000bbbe521a34ec1e777374f99f4 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Mon, 27 Mar 2023 15:37:56 -0400 Subject: [PATCH 078/150] feat(WIP): credential definitions Signed-off-by: Daniel Bluhm --- .../anoncreds/anoncreds/anoncreds_registry.py | 65 ++-- .../anoncreds/anoncreds/base_registry.py | 59 ++-- .../default/did_indy_registry/registry.py | 4 +- .../default/did_web_registry/registry.py | 4 +- .../default/legacy_indy_registry/registry.py | 216 +++++++------ .../anoncreds/anoncreds/issuer.py | 219 +++++++++---- .../anoncreds/anoncreds/routes.py | 153 ++++----- aries_cloudagent/anoncreds/issuer.py | 41 ++- .../anoncreds/models/anoncreds_cred_def.py | 301 ++++++++++++------ .../anoncreds/models/anoncreds_schema.py | 39 ++- aries_cloudagent/ledger/base.py | 65 ++++ 11 files changed, 717 insertions(+), 449 deletions(-) diff --git a/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py b/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py index f377f29295..cc7d6f8fd0 100644 --- a/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py @@ -1,16 +1,17 @@ """AnonCreds Registry""" -import itertools import logging -from typing import List, Optional, Sequence +from typing import List, Optional + -from ...config.injection_context import InjectionContext from ...core.profile import Profile from ..models.anoncreds_cred_def import ( - AnonCredsRegistryGetCredentialDefinition, AnonCredsRegistryGetRevocationList, AnonCredsRegistryGetRevocationRegistryDefinition, + CredDef, + CredDefResult, + GetCredDefResult, ) -from ..models.anoncreds_schema import GetSchemaResult, SchemaResult +from ..models.anoncreds_schema import AnonCredsSchema, GetSchemaResult, SchemaResult from .base_registry import ( AnonCredsRegistrationError, AnonCredsResolutionError, @@ -40,7 +41,7 @@ def register(self, registry: BaseAnonCredsHandler): if isinstance(registry, BaseAnonCredsRegistrar): self.registrars.append(registry) - async def _resolver_for_identifier(self, identifier: str): + async def _resolver_for_identifier(self, identifier: str) -> BaseAnonCredsResolver: resolvers = [ resolver for resolver in self.resolvers @@ -52,7 +53,9 @@ async def _resolver_for_identifier(self, identifier: str): ) return resolvers[0] - async def _registrar_for_identifier(self, identifier: str): + async def _registrar_for_identifier( + self, identifier: str + ) -> BaseAnonCredsRegistrar: registrars = [ registrar for registrar in self.registrars @@ -64,9 +67,6 @@ async def _registrar_for_identifier(self, identifier: str): ) return registrars[0] - async def setup(self, context: InjectionContext): - """Setup method.""" - async def get_schema(self, profile: Profile, schema_id: str) -> GetSchemaResult: """Get a schema from the registry.""" resolver = await self._resolver_for_identifier(schema_id) @@ -75,21 +75,16 @@ async def get_schema(self, profile: Profile, schema_id: str) -> GetSchemaResult: async def register_schema( self, profile: Profile, - issuer_id: str, - name: str, - version: str, - attr_names: Sequence[str], + schema: AnonCredsSchema, options: Optional[dict] = None, ) -> SchemaResult: """Register a schema on the registry.""" - registrar = await self._registrar_for_identifier(issuer_id) - return await registrar.register_schema( - profile, issuer_id, name, version, attr_names, options - ) + registrar = await self._registrar_for_identifier(schema.issuer_id) + return await registrar.register_schema(profile, schema, options) async def get_credential_definition( self, profile: Profile, credential_definition_id: str - ) -> AnonCredsRegistryGetCredentialDefinition: + ) -> GetCredDefResult: """Get a credential definition from the registry.""" resolver = await self._resolver_for_identifier(credential_definition_id) return await resolver.get_credential_definition( @@ -97,33 +92,23 @@ async def get_credential_definition( credential_definition_id, ) - async def get_credential_definitions(self, profile: Profile, filter: dict): - """Get credential definitions id's from the registry.""" - results = [ - await resolver.get_credential_definitions(profile, filter) - for resolver in self.resolvers - ] - return itertools.chain.from_iterable(results) - - # TODO: determine keyword arguments async def register_credential_definition( self, profile: Profile, - schema_id: str, - support_revocation: bool, - tag: str, - rev_reg_size: int, - issuer_id: str, - ): + schema: GetSchemaResult, + credential_definition: CredDef, + options: Optional[dict] = None, + ) -> CredDefResult: """Register a credential definition on the registry.""" - registrar = await self._registrar_for_identifier("something") + registrar = await self._registrar_for_identifier( + credential_definition.issuer_id + ) + return await registrar.register_credential_definition( profile, - schema_id, - support_revocation, - tag, - rev_reg_size, - issuer_id, + schema, + credential_definition, + options, ) async def get_revocation_registry_definition( diff --git a/aries_cloudagent/anoncreds/anoncreds/base_registry.py b/aries_cloudagent/anoncreds/anoncreds/base_registry.py index 196b566397..00fb3352f4 100644 --- a/aries_cloudagent/anoncreds/anoncreds/base_registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/base_registry.py @@ -1,21 +1,18 @@ """Base Registry""" from abc import ABC, abstractmethod -from typing import Generic, Optional, Pattern, Sequence, Tuple, TypeVar - -from aries_cloudagent.core.error import BaseError +from typing import Generic, Optional, Pattern, Tuple, TypeVar from ...config.injection_context import InjectionContext +from ...core.error import BaseError from ...core.profile import Profile from ..models.anoncreds_cred_def import ( - AnonCredsRegistryGetCredentialDefinition, AnonCredsRegistryGetRevocationList, AnonCredsRegistryGetRevocationRegistryDefinition, - AnonCredsRegistryGetRevocationRegistryDefinitions, -) -from ..models.anoncreds_schema import ( - GetSchemaResult, - SchemaResult, + CredDef, + CredDefResult, + GetCredDefResult, ) +from ..models.anoncreds_schema import AnonCredsSchema, GetSchemaResult, SchemaResult T = TypeVar("T") @@ -28,6 +25,12 @@ class BaseAnonCredsError(BaseError): class AnonCredsObjectNotFound(BaseAnonCredsError): """Raised when object is not found in resolver.""" + def __init__( + self, message: Optional[str] = None, resolution_metadata: Optional[dict] = None + ): + super().__init__(message, resolution_metadata) + self.resolution_metadata = resolution_metadata + class AnonCredsRegistrationError(BaseAnonCredsError): """Raised when registering an AnonCreds object fails.""" @@ -50,16 +53,18 @@ def message(self): return super().message -class AnonCredsSchemaAlreadyExists(AnonCredsObjectAlreadyExists[Tuple[str, dict]]): +class AnonCredsSchemaAlreadyExists( + AnonCredsObjectAlreadyExists[Tuple[str, AnonCredsSchema]] +): """Raised when a schema already exists.""" @property def schema_id(self): - return self.obj[0] + return self.obj[0] if self.obj else None @property def schema(self): - return self.obj[1] + return self.obj[1] if self.obj else None class AnonCredsResolutionError(BaseAnonCredsError): @@ -83,30 +88,24 @@ async def setup(self, context: InjectionContext): class BaseAnonCredsResolver(BaseAnonCredsHandler): @abstractmethod - async def get_schema(self, schema_id: str) -> GetSchemaResult: + async def get_schema(self, profile: Profile, schema_id: str) -> GetSchemaResult: """Get a schema from the registry.""" @abstractmethod async def get_credential_definition( - self, credential_definition_id: str - ) -> AnonCredsRegistryGetCredentialDefinition: + self, profile: Profile, credential_definition_id: str + ) -> GetCredDefResult: """Get a credential definition from the registry.""" @abstractmethod async def get_revocation_registry_definition( - self, revocation_registry_id: str + self, profile: Profile, revocation_registry_id: str ) -> AnonCredsRegistryGetRevocationRegistryDefinition: """Get a revocation registry definition from the registry.""" - @abstractmethod - async def get_revocation_registry_definitions( - self, filter: dict - ) -> AnonCredsRegistryGetRevocationRegistryDefinitions: - """Get a revocation registry definition ids from the registry.""" - @abstractmethod async def get_revocation_list( - self, revocation_registry_id: str, timestamp: str + self, profile: Profile, revocation_registry_id: str, timestamp: str ) -> AnonCredsRegistryGetRevocationList: """Get a revocation list from the registry.""" @@ -116,17 +115,19 @@ class BaseAnonCredsRegistrar(BaseAnonCredsHandler): async def register_schema( self, profile: Profile, - issuer_id: str, - name: str, - version: str, - attr_names: Sequence[str], + schema: AnonCredsSchema, options: Optional[dict] = None, ) -> SchemaResult: """Register a schema on the registry.""" - # TODO: determine keyword arguments @abstractmethod - async def register_credential_definition(self): + async def register_credential_definition( + self, + profile: Profile, + schema: GetSchemaResult, + credential_definition: CredDef, + options: Optional[dict] = None, + ) -> CredDefResult: """Register a credential definition on the registry.""" # TODO: determine keyword arguments diff --git a/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py index 6a6e04c7b9..d3797e7ee5 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py @@ -6,7 +6,7 @@ from .....config.injection_context import InjectionContext from .....core.profile import Profile from ....models.anoncreds_cred_def import ( - AnonCredsRegistryGetCredentialDefinition, + GetCredDefResult, AnonCredsRegistryGetRevocationList, AnonCredsRegistryGetRevocationRegistryDefinition, ) @@ -48,7 +48,7 @@ async def register_schema( async def get_credential_definition( self, profile: Profile, cred_def_id: str - ) -> AnonCredsRegistryGetCredentialDefinition: + ) -> GetCredDefResult: """Get a credential definition from the registry.""" async def get_credential_definitions(self, profile: Profile, filter: str): diff --git a/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py index 39b2e6a666..7c0536ccbe 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py @@ -5,7 +5,7 @@ from .....config.injection_context import InjectionContext from .....core.profile import Profile from ....models.anoncreds_cred_def import ( - AnonCredsRegistryGetCredentialDefinition, + GetCredDefResult, AnonCredsRegistryGetRevocationList, AnonCredsRegistryGetRevocationRegistryDefinition, ) @@ -45,7 +45,7 @@ async def register_schema( async def get_credential_definition( self, profile, credential_definition_id: str - ) -> AnonCredsRegistryGetCredentialDefinition: + ) -> GetCredDefResult: """Get a credential definition from the registry.""" async def get_credential_definitions(self, profile: Profile, filter: str): diff --git a/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py index 24e0b54655..5d031a1a69 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py @@ -2,9 +2,7 @@ import logging import re from asyncio import shield -from typing import Optional, Pattern, Sequence - -from anoncreds import Schema +from typing import Optional, Pattern from .....config.injection_context import InjectionContext from .....core.profile import Profile @@ -15,16 +13,17 @@ GET_CRED_DEF, IndyLedgerRequestsExecutor, ) -from .....messaging.credential_definitions.util import notify_cred_def_event from .....multitenant.base import BaseMultitenantManager from .....revocation.error import RevocationError from .....revocation.indy import IndyRevocation from .....storage.error import StorageNotFoundError from ....issuer import AnonCredsIssuer, AnonCredsIssuerError from ....models.anoncreds_cred_def import ( - AnonCredsCredentialDefinition, - AnonCredsCredentialDefinitionValue, - AnonCredsRegistryGetCredentialDefinition, + CredDef, + CredDefState, + CredDefValue, + CredDefResult, + GetCredDefResult, AnonCredsRegistryGetRevocationList, AnonCredsRegistryGetRevocationRegistryDefinition, ) @@ -35,6 +34,8 @@ SchemaState, ) from ...base_registry import ( + AnonCredsObjectAlreadyExists, + AnonCredsObjectNotFound, AnonCredsRegistrationError, AnonCredsResolutionError, AnonCredsSchemaAlreadyExists, @@ -44,6 +45,9 @@ LOGGER = logging.getLogger(__name__) +DEFAULT_CRED_DEF_TAG = "default" +DEFAULT_SIGNATURE_TYPE = "CL" + class LegacyIndyRegistry(BaseAnonCredsResolver, BaseAnonCredsRegistrar): """LegacyIndyRegistry""" @@ -60,6 +64,29 @@ async def setup(self, context: InjectionContext): """Setup.""" print("Successfully registered LegacyIndyRegistry") + @staticmethod + def make_schema_id(schema: AnonCredsSchema) -> str: + """Derive the ID for a schema.""" + return f"{schema.issuer_id}:2:{schema.name}:{schema.version}" + + @staticmethod + def make_cred_def_id( + schema: GetSchemaResult, + cred_def: CredDef, + ) -> str: + """Derive the ID for a credential definition.""" + signature_type = cred_def.type or DEFAULT_SIGNATURE_TYPE + tag = cred_def.tag or DEFAULT_CRED_DEF_TAG + + try: + seq_no = str(schema.schema_metadata["seqNo"]) + except KeyError as err: + raise AnonCredsRegistrationError( + "Legacy Indy only supports schemas from Legacy Indy" + ) from err + + return f"{cred_def.issuer_id}:3:{signature_type}:{seq_no}:{tag}" + async def get_schema(self, profile: Profile, schema_id: str) -> GetSchemaResult: """Get a schema from the registry.""" @@ -82,6 +109,12 @@ async def get_schema(self, profile: Profile, schema_id: str) -> GetSchemaResult: async with ledger: try: schema = await ledger.get_schema(schema_id) + if schema is None: + raise AnonCredsObjectNotFound( + f"Credential definition not found: {schema_id}", + {"ledger_id": ledger_id}, + ) + anonscreds_schema = AnonCredsSchema( issuer_id=schema["id"].split(":")[0], attr_names=schema["attrNames"], @@ -102,16 +135,12 @@ async def get_schema(self, profile: Profile, schema_id: str) -> GetSchemaResult: async def register_schema( self, profile: Profile, - issuer_id: str, - name: str, - version: str, - attr_names: Sequence[str], + schema: AnonCredsSchema, options: Optional[dict] = None, ) -> SchemaResult: """Register a schema on the registry.""" - schema = Schema.create(name, version, issuer_id, attr_names) - schema_id = f"{issuer_id}:2:{name}:{version}" + schema_id = self.make_schema_id(schema) # Assume endorser role on the network, no option for 3rd-party endorser ledger = profile.inject_or(BaseLedger) @@ -124,13 +153,12 @@ async def register_schema( # Translate schema into format expected by Indy LOGGER.debug("Registering schema: %s", schema_id) - anoncreds_schema = schema.to_dict() indy_schema = { "ver": "1.0", "id": schema_id, - "name": anoncreds_schema["name"], - "version": anoncreds_schema["version"], - "attrNames": anoncreds_schema["attrNames"], + "name": schema.name, + "version": schema.version, + "attrNames": schema.attr_names, "seqNo": None, } LOGGER.debug("schema value: %s", indy_schema) @@ -139,7 +167,14 @@ async def register_schema( try: seq_no = await shield(ledger.send_schema(schema_id, indy_schema)) except LedgerObjectAlreadyExistsError as err: - raise AnonCredsSchemaAlreadyExists(err.message, err.obj) + indy_schema = err.obj[1] + schema = AnonCredsSchema( + name=indy_schema["name"], + version=indy_schema["version"], + attr_names=indy_schema["attrNames"], + issuer_id=indy_schema["id"].split(":")[0], + ) + raise AnonCredsSchemaAlreadyExists(err.message, (err.obj[0], schema)) except (AnonCredsIssuerError, LedgerError) as err: raise AnonCredsRegistrationError("Failed to register schema") from err @@ -148,7 +183,7 @@ async def register_schema( schema_state=SchemaState( state=SchemaState.STATE_FINISHED, schema_id=schema_id, - schema_def=AnonCredsSchema.deserialize(anoncreds_schema), + schema=schema, ), registration_metadata={}, schema_metadata={"seqNo": seq_no}, @@ -156,7 +191,7 @@ async def register_schema( async def get_credential_definition( self, profile: Profile, cred_def_id: str - ) -> AnonCredsRegistryGetCredentialDefinition: + ) -> GetCredDefResult: """Get a credential definition from the registry.""" async with profile.session() as session: @@ -165,6 +200,7 @@ async def get_credential_definition( ledger_exec_inst = IndyLedgerRequestsExecutor(profile) else: ledger_exec_inst = session.inject(IndyLedgerRequestsExecutor) + ledger_id, ledger = await ledger_exec_inst.get_ledger_for_identifier( cred_def_id, txn_record_type=GET_CRED_DEF, @@ -177,38 +213,40 @@ async def get_credential_definition( async with ledger: cred_def = await ledger.get_credential_definition(cred_def_id) - anoncreds_credential_definition_value = AnonCredsCredentialDefinitionValue( - primary=cred_def["value"] - ) - anoncreds_credential_definition = AnonCredsCredentialDefinition( + + if cred_def is None: + raise AnonCredsObjectNotFound( + f"Credential definition not found: {cred_def_id}", + {"ledger_id": ledger_id}, + ) + + cred_def_value = CredDefValue.deserialize(cred_def["value"]) + anoncreds_credential_definition = CredDef( issuer_id=cred_def["id"].split(":")[0], schema_id=cred_def["schemaId"], type=cred_def["type"], tag=cred_def["tag"], - value=anoncreds_credential_definition_value, + value=cred_def_value, ) - anoncreds_registry_get_credential_definition = ( - AnonCredsRegistryGetCredentialDefinition( - credential_definition=anoncreds_credential_definition, - credential_definition_id=cred_def["id"], - resolution_metadata={}, - credential_definition_metadata={}, - ) + anoncreds_registry_get_credential_definition = GetCredDefResult( + credential_definition=anoncreds_credential_definition, + credential_definition_id=cred_def["id"], + resolution_metadata={}, + credential_definition_metadata={}, ) return anoncreds_registry_get_credential_definition async def register_credential_definition( self, profile: Profile, - schema_id: str, - support_revocation: bool, - tag: str, - rev_reg_size: int, - issuer_id: str, - options, # TODO: handle options - ): + schema: GetSchemaResult, + credential_definition: CredDef, + options: Optional[dict] = None, + ) -> CredDefResult: """Register a credential definition on the registry.""" + cred_def_id = self.make_cred_def_id(schema, credential_definition) + ledger = profile.inject_or(BaseLedger) if not ledger: reason = "No ledger available" @@ -216,68 +254,54 @@ async def register_credential_definition( reason += ": missing wallet-type?" raise AnonCredsRegistrationError(reason) + # Check if in wallet but not on ledger issuer = profile.inject(AnonCredsIssuer) - try: # even if in wallet, send it and raise if erroneously so + if await issuer.credential_definition_in_wallet(cred_def_id): + try: + await self.get_credential_definition(profile, cred_def_id) + except AnonCredsObjectNotFound as err: + raise AnonCredsRegistrationError( + f"Credential definition with id {cred_def_id} already " + "exists in wallet but not on the ledger" + ) from err + + try: async with ledger: - (cred_def_id, cred_def, novel) = await shield( - ledger.create_and_send_credential_definition( - issuer, - schema_id, - signature_type=None, - tag=tag, - support_revocation=support_revocation, + seq_no = await shield( + ledger.send_credential_definition( + credential_definition.schema_id, + cred_def_id, + credential_definition.serialize(), write_ledger=True, - endorser_did=issuer_id, + endorser_did=credential_definition.issuer_id, ) ) - - except (AnonCredsIssuerError, LedgerError) as e: - raise AnonCredsRegistrationError(e) - - issuer_did = cred_def_id.split(":")[0] - meta_data = { - "context": { - "schema_id": schema_id, - "cred_def_id": cred_def_id, - "issuer_did": issuer_did, - "support_revocation": support_revocation, - "novel": novel, - "tag": tag, - "rev_reg_size": rev_reg_size, - }, - "processing": { - "create_pending_rev_reg": True, - }, - } - - # Notify event - meta_data["processing"]["auto_create_rev_reg"] = True - await notify_cred_def_event(profile, cred_def_id, meta_data) - - return { - "job_id": None, - "credential_definition_state": { - "state": "finished", - "credential_definition_id": cred_def_id, - "credential_definition": { - "issuerId": issuer_did, - "schemaId": schema_id, - "type": "CL", - "tag": tag, - "value": { - "primary": { - "n": cred_def["value"]["primary"]["n"], - "r": cred_def["value"]["primary"]["r"], - "rctxt": cred_def["value"]["primary"]["rctxt"], - "s": cred_def["value"]["primary"]["s"], - "z": cred_def["value"]["primary"]["z"], - } - }, - }, - }, - "registration_metadata": {}, - "credential_definition_metadata": {}, - } + except LedgerObjectAlreadyExistsError as err: + if await issuer.credential_definition_in_wallet(cred_def_id): + raise AnonCredsObjectAlreadyExists( + f"Credential definition with id {cred_def_id} " + "already exists in wallet and on ledger.", + ) from err + else: + raise AnonCredsObjectAlreadyExists( + f"Credential definition {cred_def_id} is on " + f"ledger but not in wallet {profile.name}" + ) from err + except (AnonCredsIssuerError, LedgerError) as err: + raise AnonCredsRegistrationError( + "Failed to register credential definition" + ) from err + + return CredDefResult( + job_id=None, + credential_definition_state=CredDefState( + state=CredDefState.STATE_FINISHED, + credential_definition_id=cred_def_id, + credential_definition=credential_definition, + ), + registration_metadata={}, + credential_definition_metadata={"seqNo": seq_no, **(options or {})}, + ) async def get_revocation_registry_definition( self, profile: Profile, rev_reg_id: str diff --git a/aries_cloudagent/anoncreds/anoncreds/issuer.py b/aries_cloudagent/anoncreds/anoncreds/issuer.py index ff9e767eb8..cf28c7703e 100644 --- a/aries_cloudagent/anoncreds/anoncreds/issuer.py +++ b/aries_cloudagent/anoncreds/anoncreds/issuer.py @@ -1,7 +1,6 @@ -"""Indy issuer implementation.""" +"""anoncreds-rs issuer implementation.""" import asyncio -import json import logging import time @@ -10,6 +9,7 @@ from aries_askar import AskarError from anoncreds import ( + Schema, Credential, CredentialDefinition, CredentialOffer, @@ -20,9 +20,11 @@ RevocationStatusList, ) + from .anoncreds_registry import AnonCredsRegistry from .base_registry import AnonCredsSchemaAlreadyExists -from ..models.anoncreds_schema import SchemaResult, SchemaState +from ..models.anoncreds_cred_def import CredDef, CredDefResult, CredDefState +from ..models.anoncreds_schema import AnonCredsSchema, SchemaResult, SchemaState from ...askar.profile import AskarProfile @@ -65,12 +67,35 @@ def profile(self) -> AskarProfile: """Accessor for the profile instance.""" return self._profile - async def create_schema( + async def _store_schema( + self, + schema_id: str, + schema: AnonCredsSchema, + state: str, + ): + """Store schema after reaching finished state.""" + try: + async with self._profile.session() as session: + await session.handle.insert( + CATEGORY_SCHEMA, + schema_id, + schema.to_json(), + { + "name": schema.name, + "version": schema.version, + "issuer_id": schema.issuer_id, + "state": state, + }, + ) + except AskarError as err: + raise AnonCredsIssuerError("Error storing schema") from err + + async def create_and_register_schema( self, issuer_id: str, - schema_name: str, - schema_version: str, - attribute_names: Sequence[str], + name: str, + version: str, + attr_names: Sequence[str], options: Optional[dict] = None, ) -> SchemaResult: """ @@ -78,12 +103,12 @@ async def create_schema( Args: issuer_id: the DID issuing the credential definition - schema_name: the schema name - schema_version: the schema version - attribute_names: a sequence of schema attribute names + name: the schema name + version: the schema version + attr_names: a sequence of schema attribute names Returns: - A tuple of the schema ID and JSON + A SchemaResult instance """ # Check if record of a similar schema already exists in our records @@ -92,35 +117,33 @@ async def create_schema( schemas = await session.handle.fetch_all( CATEGORY_SCHEMA, { - "name": schema_name, - "version": schema_version, + "name": name, + "version": version, "issuer_id": issuer_id, }, limit=1, ) if schemas: raise AnonCredsSchemaAlreadyExists( - f"Schema with {schema_name}: {schema_version} " - f"already exists for {issuer_id}" + f"Schema with {name}: {version} " f"already exists for {issuer_id}" ) + # TODO Do we even need to create the native object here? + schema = Schema.create(name, version, issuer_id, attr_names) try: anoncreds_registry = self._profile.inject(AnonCredsRegistry) schema_result = await anoncreds_registry.register_schema( self.profile, - issuer_id, - schema_name, - schema_version, - attribute_names, + AnonCredsSchema.from_native(schema), options, ) - if schema_result.schema_state.state == SchemaState.STATE_FINISHED: - await self.store_schema( - schema_result.schema_state.schema_id, - schema_result.schema_state.schema_def.serialize(), - ) + await self._store_schema( + schema_result.schema_state.schema_id, + schema_result.schema_state.schema, + state=schema_result.schema_state.state, + ) return schema_result @@ -128,38 +151,40 @@ async def create_schema( # If we find that we've previously written a schema that looks like # this one before but that schema is not in our wallet, add it to # the wallet so we can return from our get schema calls - await self.store_schema(err.schema_id, err.schema) - raise AnonCredsIssuerError( - "Schema already exists but was not in wallet; stored in wallet" - ) from err + if err.schema_id and err.schema: + await self._store_schema( + err.schema_id, err.schema, SchemaState.STATE_FINISHED + ) + raise AnonCredsIssuerError( + "Schema already exists but was not in wallet; stored in wallet" + ) from err + raise except AnoncredsError as err: raise AnonCredsIssuerError("Error creating schema") from err - except AskarError as err: - raise AnonCredsIssuerError("Error storing schema") from err - async def store_schema( - self, - schema_id: str, - schema: dict, - ): - """Store schema after reaching finished state.""" - async with self._profile.session() as session: - await session.handle.insert( - CATEGORY_SCHEMA, - schema_id, - json.dumps(schema), - { - "name": schema["name"], - "version": schema["version"], - "issuer_id": schema["issuerId"], - }, - ) + async def finish_schema(self, schema_id: str): + try: + async with self._profile.transaction() as txn: + entry = await txn.handle.fetch( + CATEGORY_SCHEMA, + schema_id, + for_update=True, + ) + entry.tags["state"] = SchemaState.STATE_FINISHED + await txn.handle.replace( + CATEGORY_SCHEMA, + schema_id, + tags=entry.tags, + ) + except AskarError as err: + raise AnonCredsIssuerError("Error marking schema as finished") from err async def get_created_schemas( self, name: Optional[str] = None, version: Optional[str] = None, issuer_id: Optional[str] = None, + state: Optional[str] = None, ) -> Sequence[str]: """Retrieve IDs of schemas previously created.""" async with self._profile.session() as session: @@ -172,6 +197,7 @@ async def get_created_schemas( "name": name, "version": version, "issuer_id": issuer_id, + "state": state, }.items() if value is not None }, @@ -202,12 +228,12 @@ async def credential_definition_in_wallet( async def create_and_store_credential_definition( self, - origin_did: str, - schema: dict, - signature_type: str = None, - tag: str = None, - support_revocation: bool = False, - ) -> Tuple[str, str]: + issuer_id: str, + schema_id: str, + tag: Optional[str] = None, + signature_type: Optional[str] = None, + options: Optional[dict] = None, + ) -> CredDefResult: """ Create a new credential definition and store it in the wallet. @@ -222,13 +248,14 @@ async def create_and_store_credential_definition( A tuple of the credential definition ID and JSON """ - schema_id = AnonCredsIssuer.make_schema_id( - origin_did, schema["name"], schema["version"] - ) - cred_def_id = AnonCredsIssuer.make_credential_definition_id( - origin_did, schema, signature_type, tag - ) + anoncreds_registry = self._profile.inject(AnonCredsRegistry) + schema_result = await anoncreds_registry.get_schema(self.profile, schema_id) + + options = options or {} + support_revocation = options.get("support_revocation", False) + try: + # Create the cred def ( cred_def, cred_def_private, @@ -237,24 +264,41 @@ async def create_and_store_credential_definition( None, lambda: CredentialDefinition.create( schema_id, - schema, - origin_did, + schema_result.schema.serialize(), + issuer_id, tag or DEFAULT_CRED_DEF_TAG, signature_type or DEFAULT_SIGNATURE_TYPE, support_revocation=support_revocation, ), ) cred_def_json = cred_def.to_json() + + # Register the cred def + result = await anoncreds_registry.register_credential_definition( + self.profile, + schema_result, + CredDef.from_native(cred_def), + options, + ) except AnoncredsError as err: raise AnonCredsIssuerError("Error creating credential definition") from err + + # Store the cred def and it's components try: + cred_def_id = result.credential_definition_state.credential_definition_id async with self._profile.transaction() as txn: await txn.handle.insert( CATEGORY_CRED_DEF, cred_def_id, cred_def_json, # Note: Indy-SDK uses a separate SchemaId record for this - tags={"schema_id": schema_id}, + tags={ + "schema_id": schema_id, + "issuer_id": issuer_id, + "schema_name": schema_result.schema.name, + "schema_version": schema_result.schema.version, + "state": result.credential_definition_state.state, + }, ) await txn.handle.insert( CATEGORY_CRED_DEF_PRIVATE, @@ -267,7 +311,56 @@ async def create_and_store_credential_definition( await txn.commit() except AskarError as err: raise AnonCredsIssuerError("Error storing credential definition") from err - return (cred_def_id, cred_def_json) + + return result + + async def finish_credential_definition(self, cred_def_id: str): + """Finish a cred def.""" + try: + async with self._profile.transaction() as txn: + entry = await txn.handle.fetch( + CATEGORY_CRED_DEF, + cred_def_id, + for_update=True, + ) + entry.tags["state"] = CredDefState.STATE_FINISHED + await txn.handle.replace( + CATEGORY_SCHEMA, + cred_def_id, + tags=entry.tags, + ) + except AskarError as err: + raise AnonCredsIssuerError( + "Error marking credential definition as finished" + ) from err + + async def get_created_credential_definitions( + self, + issuer_id: Optional[str] = None, + schema_id: Optional[str] = None, + schema_name: Optional[str] = None, + schema_version: Optional[str] = None, + state: Optional[str] = None, + ) -> Sequence[str]: + """Retrieve IDs of credential definitions previously created.""" + async with self._profile.session() as session: + # TODO limit? scan? + credential_definitions = await session.handle.fetch_all( + CATEGORY_CRED_DEF, + { + key: value + for key, value in { + "issuer_id": issuer_id, + "schema_id": schema_id, + "schema_name": schema_name, + "schema_version": schema_version, + "state": state, + }.items() + if value is not None + }, + ) + # entry.name was stored as the credential_definition's ID + return [entry.name for entry in credential_definitions] async def create_credential_offer(self, credential_definition_id: str) -> str: """ diff --git a/aries_cloudagent/anoncreds/anoncreds/routes.py b/aries_cloudagent/anoncreds/anoncreds/routes.py index 7bd29c8174..119eb13756 100644 --- a/aries_cloudagent/anoncreds/anoncreds/routes.py +++ b/aries_cloudagent/anoncreds/anoncreds/routes.py @@ -14,25 +14,18 @@ from .anoncreds_registry import AnonCredsRegistry from ..issuer import AnonCredsIssuer from ..models.anoncreds_cred_def import ( - AnonCredsCredentialDefinitionSchema, - AnonCredsRegistryGetCredentialDefinitionSchema, + CredDefSchema, + GetCredDefResultSchema, ) from ..models.anoncreds_schema import ( AnonCredsSchemaSchema, SchemaResultSchema, GetSchemaResultSchema, ) -from aries_cloudagent.anoncreds.models.anoncreds_valid import ( - ANONCREDS_SCHEMA_ID, - ANONCREDS_VERSION, -) from ...admin.request_context import AdminRequestContext from ...messaging.models.openapi import OpenAPISchema from ...messaging.valid import ( - GENERIC_DID, - INDY_CRED_DEF_ID, - INDY_SCHEMA_ID, UUIDFour, ) @@ -44,9 +37,7 @@ class SchemaIdMatchInfo(OpenAPISchema): """Path parameters and validators for request taking schema id.""" - schema_id = fields.Str( - data_key="schemaId", description="Schema identifier", **INDY_SCHEMA_ID - ) + schema_id = fields.Str(data_key="schemaId", description="Schema identifier") class CredIdMatchInfo(OpenAPISchema): @@ -57,67 +48,44 @@ class CredIdMatchInfo(OpenAPISchema): ) -class CredDefSchema(OpenAPISchema): +class InnerCredDefSchema(OpenAPISchema): """Parameters and validators for credential definition.""" - # tag = fields.Str( - # description="""The tag value passed in by the Issuer to - # an AnonCred's Credential Definition create and store implementation.""" - # ) - schemaId = fields.Str( - data_key="schemaId", description="Schema identifier", **ANONCREDS_SCHEMA_ID - ) + tag = fields.Str(description="Credential definition tag") + schemaId = fields.Str(data_key="schemaId", description="Schema identifier") issuerId = fields.Str( description="Issuer Identifier of the credential definition or schema", - **GENERIC_DID, - ) # TODO: get correct validator - supportRevocation = fields.Bool() - revocationRegistrySize = fields.Int() + ) class CredDefPostOptionsSchema(OpenAPISchema): """Parameters and validators for credential definition options.""" - endorserConnectionId = fields.Str() - supportRevocation = fields.Bool() - revocationRegistrySize = fields.Int() + endorser_connection_id = fields.Str(required=False) + support_revocation = fields.Bool(required=False) + revocation_registry_size = fields.Int(required=False) -class CredDefPostQueryStringSchema(OpenAPISchema): +class CredDefPostRequestSchema(OpenAPISchema): """Parameters and validators for query string in create credential definition.""" - credentialDefinition = fields.Nested(CredDefSchema()) + credential_definition = fields.Nested(InnerCredDefSchema()) options = fields.Nested(CredDefPostOptionsSchema()) class CredDefsQueryStringSchema(OpenAPISchema): """Parameters and validators for credential definition list query.""" - credentialDefinitionId = fields.Str( - description="Credential definition identifier", - **INDY_CRED_DEF_ID, - data_key="credentialDefinitionId", - ) issuer_id = fields.Str( - description="Issuer Identifier of the credential definition or schema", - **GENERIC_DID, - data_key="issuerId", - ) # TODO: get correct validator - schema_id = fields.Str( - data_key="schemaId", description="Schema identifier", **ANONCREDS_SCHEMA_ID + description="Issuer Identifier of the credential definition", ) - schema_issuer_id = fields.Str( - description="Issuer Identifier of the credential definition or schema", - **GENERIC_DID, - data_key="schemaIssuerId", - ) # TODO: get correct validator + schema_id = fields.Str(data_key="schemaId", description="Schema identifier") schema_name = fields.Str( description="Schema name", - example=ANONCREDS_SCHEMA_ID["example"].split(":")[2], - data_key="schemaName", ) - schema_version = fields.Str( - description="Schema version", **ANONCREDS_VERSION, data_key="schemaVersion" + schema_version = fields.Str(description="Schema version") + state = fields.Str( + description="Credential definition state", ) @@ -127,9 +95,8 @@ class CredDefState(OpenAPISchema): state = fields.Str() # TODO: create validator for only possible states credential_definition_id = fields.Str( description="Credential definition identifier", - **INDY_CRED_DEF_ID, ) - credential_definition = fields.Nested(AnonCredsCredentialDefinitionSchema()) + credential_definition = fields.Nested(CredDefSchema()) class PostCredDefResponseSchema(OpenAPISchema): @@ -141,12 +108,6 @@ class PostCredDefResponseSchema(OpenAPISchema): credential_definition_metadata = fields.Dict() -class GetCredDefsResponseSchema(OpenAPISchema): - """Parameters and validators for credential definition list all response.""" - - credential_definition_id = fields.Str() - - class SchemaPostOptionSchema(OpenAPISchema): """Parameters and validators for schema options.""" @@ -204,9 +165,9 @@ async def schemas_post(request: web.BaseRequest): """ context: AdminRequestContext = request["context"] - request_data = await request.json() - options = request_data.get("option") - schema_data = request_data.get("schema") + body = await request.json() + options = body.get("option") + schema_data = body.get("schema") issuer_id = schema_data.get("issuerId") attr_names = schema_data.get("attrNames") @@ -214,7 +175,7 @@ async def schemas_post(request: web.BaseRequest): version = schema_data.get("version") issuer = context.inject(AnonCredsIssuer) - result = await issuer.create_schema( + result = await issuer.create_and_register_schema( issuer_id, name, version, attr_names, options=options ) return web.json_response(result.serialize()) @@ -290,7 +251,7 @@ async def schemas_get(request: web.BaseRequest): @docs(tags=["anoncreds"], summary="") -@request_schema(CredDefPostQueryStringSchema()) +@request_schema(CredDefPostRequestSchema()) @response_schema(PostCredDefResponseSchema(), 200, description="") async def cred_def_post(request: web.BaseRequest): """Request handler for creating . @@ -301,34 +262,27 @@ async def cred_def_post(request: web.BaseRequest): """ context: AdminRequestContext = request["context"] - anon_creds_registry = context.inject(AnonCredsRegistry) - request_data = await request.json() - options = request_data.get("option") - data = request_data.get("credentialDefinition") - - # TODO: find out if we need a model for this input. - # cred_def = { - # "issuer_id": data.get("issuerId"), - # "schema_id": data.get("schemaId"), - # # "tag":data.get("tag"), - # "support_revocation": data.get("supportRevocation"), - # "revocation_registrySize": data.get("revocationRegistrySize"), - # } - result = await anon_creds_registry.register_credential_definition( - profile=context.profile, - schema_id=data["schemaId"], - support_revocation=data["supportRevocation"], - tag=None, - rev_reg_size=data["revocationRegistrySize"], - issuer_id=data["issuerId"], + body = await request.json() + options = body.get("options") + cred_def = body.get("credential_definition") + issuer_id = cred_def.get("issuerId") + schema_id = cred_def.get("schemaId") + tag = cred_def.get("tag") + + issuer = context.inject(AnonCredsIssuer) + result = await issuer.create_and_store_credential_definition( + issuer_id, + schema_id, + tag, options=options, ) - return web.json_response(result) + + return web.json_response(result.serialize()) @docs(tags=["anoncreds"], summary="") @match_info_schema(CredIdMatchInfo()) -@response_schema(AnonCredsRegistryGetCredentialDefinitionSchema(), 200, description="") +@response_schema(GetCredDefResultSchema(), 200, description="") async def cred_def_get(request: web.BaseRequest): """Request handler for getting credential definition. @@ -346,6 +300,16 @@ async def cred_def_get(request: web.BaseRequest): return web.json_response(result.serialize()) +class GetCredDefsResponseSchema(OpenAPISchema): + """AnonCredsRegistryGetCredDefsSchema""" + + credential_definition_ids = fields.List( + fields.Str( + description="credential definition identifiers", + ) + ) + + @docs(tags=["anoncreds"], summary="") @querystring_schema(CredDefsQueryStringSchema()) @response_schema(GetCredDefsResponseSchema(), 200, description="") @@ -358,19 +322,14 @@ async def cred_defs_get(request: web.BaseRequest): """ context: AdminRequestContext = request["context"] - anon_creds_registry = context.inject(AnonCredsRegistry) - query = request.query - - filter = { - "cred_def_id": query.get("credentialDefinitionId"), - "issuer_id": query.get("issuerId"), - "schema_id": query.get("schemaId"), - "schema_issuer_id": query.get("schemaIssuerId"), - "schema_name": query.get("schemaName"), - "schema_version": query.get("schemaVersion"), - } - cred_def_ids = await anon_creds_registry.get_credential_definitions( - context.profile, filter + issuer = context.inject(AnonCredsIssuer) + + cred_def_ids = await issuer.get_created_credential_definitions( + issuer_id=request.query.get("issuer_id"), + schema_id=request.query.get("schema_id"), + schema_name=request.query.get("schema_name"), + schema_version=request.query.get("schema_version"), + state=request.query.get("state"), ) return web.json_response(cred_def_ids) diff --git a/aries_cloudagent/anoncreds/issuer.py b/aries_cloudagent/anoncreds/issuer.py index a4d42ba257..8b4efb94ca 100644 --- a/aries_cloudagent/anoncreds/issuer.py +++ b/aries_cloudagent/anoncreds/issuer.py @@ -3,7 +3,7 @@ from abc import ABC, ABCMeta, abstractmethod from typing import Optional, Sequence, Tuple -from anoncreds import Schema +from aries_cloudagent.anoncreds.models.anoncreds_cred_def import CredDefResult from .models.anoncreds_schema import SchemaResult @@ -41,12 +41,12 @@ def make_schema_id(origin_did: str, schema_name: str, schema_version: str) -> st return f"{origin_did}:2:{schema_name}:{schema_version}" @abstractmethod - async def create_schema( + async def create_and_register_schema( self, issuer_id: str, - schema_name: str, - schema_version: str, - attribute_names: Sequence[str], + name: str, + version: str, + attr_names: Sequence[str], options: Optional[dict] = None, ) -> SchemaResult: """ @@ -64,12 +64,8 @@ async def create_schema( """ @abstractmethod - async def store_schema( - self, - schema_id: str, - schema: Schema, - ) -> Tuple[str, str]: - """Store a schema in the wallet.""" + async def finish_schema(self, schema_id: str): + """Mark a schema as finished in the wallet.""" @abstractmethod async def get_created_schemas( @@ -103,12 +99,12 @@ async def credential_definition_in_wallet( @abstractmethod async def create_and_store_credential_definition( self, - origin_did: str, - schema: dict, - signature_type: str = None, - tag: str = None, - support_revocation: bool = False, - ) -> Tuple[str, str]: + issuer_id: str, + schema_id: str, + tag: Optional[str] = None, + signature_type: Optional[str] = None, + options: Optional[dict] = None, + ) -> CredDefResult: """ Create a new credential definition and store it in the wallet. @@ -124,6 +120,17 @@ async def create_and_store_credential_definition( """ + @abstractmethod + async def get_created_credential_definitions( + self, + issuer_id: Optional[str] = None, + schema_id: Optional[str] = None, + schema_name: Optional[str] = None, + schema_version: Optional[str] = None, + state: Optional[str] = None, + ) -> Sequence[str]: + """Get created credential definitions.""" + @abstractmethod async def create_credential_offer(self, credential_definition_id) -> str: """ diff --git a/aries_cloudagent/anoncreds/models/anoncreds_cred_def.py b/aries_cloudagent/anoncreds/models/anoncreds_cred_def.py index 6e02d773e1..8fd65ef917 100644 --- a/aries_cloudagent/anoncreds/models/anoncreds_cred_def.py +++ b/aries_cloudagent/anoncreds/models/anoncreds_cred_def.py @@ -1,13 +1,11 @@ """Anoncreds cred def OpenAPI validators""" from typing import Any, Dict, List, Optional from typing_extensions import Literal +from anoncreds import CredentialDefinition -from marshmallow import EXCLUDE, Schema, fields +from marshmallow import EXCLUDE, fields +from marshmallow.validate import OneOf -from aries_cloudagent.anoncreds.models.anoncreds_valid import ( - ANONCREDS_SCHEMA_ID, - ANONCREDS_VERSION, -) from aries_cloudagent.messaging.models.base import BaseModel, BaseModelSchema from aries_cloudagent.messaging.valid import ( GENERIC_DID, @@ -15,17 +13,14 @@ NUM_STR_WHOLE, ) -from ...messaging.models.openapi import OpenAPISchema -from .anoncreds_schema import AnonCredsSchema - -class PrimarySchema(BaseModel): +class CredDefValuePrimary(BaseModel): """PrimarySchema""" class Meta: """PrimarySchema metadata.""" - schema_class = "PrimarySchemaSchema" + schema_class = "CredDefValuePrimarySchema" def __init__(self, n: str, s: str, r: dict, rctxt: str, z: str, **kwargs): super().__init__(**kwargs) @@ -36,66 +31,120 @@ def __init__(self, n: str, s: str, r: dict, rctxt: str, z: str, **kwargs): self.z = z -class PrimarySchemaSchema(BaseModelSchema): - """Parameters and validators for credential definition primary.""" +class CredDefValuePrimarySchema(BaseModelSchema): + """Cred def value primary schema.""" class Meta: - """PrimarySchema metadata.""" + """CredDefValuePrimarySchema metadata.""" - model_class = AnonCredsSchema + model_class = CredDefValuePrimary unknown = EXCLUDE n = fields.Str(**NUM_STR_WHOLE) s = fields.Str(**NUM_STR_WHOLE) - r = fields.Nested( - Schema.from_dict( - { - "master_secret": fields.Str(**NUM_STR_WHOLE), - "number": fields.Str(**NUM_STR_WHOLE), - "remainder": fields.Str(**NUM_STR_WHOLE), - } - ), - name="CredDefValuePrimaryRSchema", - ) + r = fields.Dict() rctxt = fields.Str(**NUM_STR_WHOLE) z = fields.Str(**NUM_STR_WHOLE) -# TODO: determine types for `primary` and `revocation` -class AnonCredsCredentialDefinitionValue(BaseModel): - """AnonCredsCredentialDefinitionValue""" +class CredDefValueRevocation(BaseModel): + """Cred def value revocation.""" + + class Meta: + """CredDefValueRevocation metadata.""" + + schema_class = "CredDefValueRevocationSchema" + + def __init__( + self, + g: str, + g_dash: str, + h: str, + h0: str, + h1: str, + h2: str, + htilde: str, + h_cap: str, + u: str, + pk: str, + y: str, + ): + self.g = g + self.g_dash = g_dash + self.h = h + self.h0 = h0 + self.h1 = h1 + self.h2 = h2 + self.htilde = htilde + self.h_cap = h_cap + self.u = u + self.pk = pk + self.y = y + + +class CredDefValueRevocationSchema(BaseModelSchema): + """Cred def value revocation schema.""" + + class Meta: + model_class = CredDefValueRevocation + unknown = EXCLUDE + + g = fields.Str(example="1 1F14F&ECB578F 2 095E45DDF417D") + g_dash = fields.Str(example="1 1D64716fCDC00C 1 0C781960FA66E3D3 2 095E45DDF417D") + h = fields.Str(example="1 16675DAE54BFAE8 2 095E45DD417D") + h0 = fields.Str(example="1 21E5EF9476EAF18 2 095E45DDF417D") + h1 = fields.Str(example="1 236D1D99236090 2 095E45DDF417D") + h2 = fields.Str(example="1 1C3AE8D1F1E277 2 095E45DDF417D") + htilde = fields.Str(example="1 1D8549E8C0F8 2 095E45DDF417D") + h_cap = fields.Str(example="1 1B2A32CF3167 1 2490FEBF6EE55 1 0000000000000000") + u = fields.Str(example="1 0C430AAB2B4710 1 1CB3A0932EE7E 1 0000000000000000") + pk = fields.Str(example="1 142CD5E5A7DC 1 153885BD903312 2 095E45DDF417D") + y = fields.Str(example="1 153558BD903312 2 095E45DDF417D 1 0000000000000000") + + +class CredDefValue(BaseModel): + """Cred def value.""" class Meta: - """AnonCredsCredentialDefinitionValue metadata.""" + """CredDefValue metadata.""" - schema_class = "AnonCredsCredentialDefinitionValueSchema" + schema_class = "CredDefValueSchema" - def __init__(self, primary: PrimarySchema, **kwargs): + def __init__( + self, primary: CredDefValuePrimary, revocation: CredDefValueRevocation, **kwargs + ): super().__init__(**kwargs) self.primary = primary - - # revocation: Optional[Any] + self.revocation = revocation -class AnonCredsCredentialDefinitionValueSchema(BaseModelSchema): - """Parameters and validators for credential definition value.""" +class CredDefValueSchema(BaseModelSchema): + """Cred def value schema.""" class Meta: - """AnonCredsCredentialDefinitionValueSchema metadata.""" + """CredDefValueSchema metadata.""" - model_class = AnonCredsCredentialDefinitionValue + model_class = CredDefValue unknown = EXCLUDE - primary = fields.Nested(PrimarySchemaSchema()) + primary = fields.Nested( + CredDefValuePrimarySchema(), + description="Primary value for credential definition", + ) + revocation = fields.Nested( + CredDefValueRevocationSchema(), + description="Revocation value for credential definition", + required=False, + ) -class AnonCredsCredentialDefinition(BaseModel): - """AnonCredsCredentialDefinition""" +class CredDef(BaseModel): + """AnonCredsCredDef""" class Meta: - """AnonCredsCredentialDefinition metadata.""" + """AnonCredsCredDef metadata.""" - schema_class = "AnonCredsCredentialDefinitionSchema" + schema_class = "CredDefSchema" def __init__( self, @@ -103,111 +152,171 @@ def __init__( schema_id: str, type: Literal["CL"], tag: str, - value: AnonCredsCredentialDefinitionValue, - **kwargs + value: CredDefValue, + **kwargs, ): + super().__init__(**kwargs) self.issuer_id = issuer_id self.schema_id = schema_id self.type = type self.tag = tag self.value = value + @classmethod + def from_native(cls, cred_def: CredentialDefinition): + """Convert a native credential definition to a CredDef object.""" + return cls.deserialize(cred_def.to_json()) + + def to_native(self): + """Convert to native anoncreds credential definition.""" + return CredentialDefinition.load(self.serialize()) + -class AnonCredsCredentialDefinitionSchema(BaseModelSchema): - """AnonCredsCredentialDefinitionSchema""" +class CredDefSchema(BaseModelSchema): + """CredDefSchema.""" class Meta: - """AnonCredsCredentialDefinitionSchema metadata.""" + """CredDefSchema metadata.""" - model_class = AnonCredsCredentialDefinition + model_class = CredDef unknown = EXCLUDE issuer_id = fields.Str( description="Issuer Identifier of the credential definition or schema", - **GENERIC_DID, data_key="issuerId", - ) # TODO: get correct validator - schema_id = fields.Str( - data_key="schemaId", description="Schema identifier", **ANONCREDS_SCHEMA_ID ) - type = fields.Str() + schema_id = fields.Str(data_key="schemaId", description="Schema identifier") + type = fields.Str(validate=OneOf(["CL"])) tag = fields.Str( description="""The tag value passed in by the Issuer to an AnonCred's Credential Definition create and store implementation.""" ) - value = fields.Nested(AnonCredsCredentialDefinitionValueSchema()) + value = fields.Nested(CredDefValueSchema()) -class AnonCredsRegistryGetCredentialDefinition(BaseModel): - """AnonCredsRegistryGetCredentialDefinition""" +class CredDefState(BaseModel): + """CredDefState.""" + + STATE_FINISHED = "finished" + STATE_FAILED = "failed" + STATE_ACTION = "action" + STATE_WAIT = "wait" class Meta: - """AnonCredsRegistryGetCredentialDefinition metadata.""" + """CredDefState metadata.""" - schema_class = "AnonCredsRegistryGetCredentialDefinitionSchema" + schema_class = "CredDefStateSchema" + + def __init__( + self, state: str, credential_definition_id: str, credential_definition: CredDef + ): + self.state = state + self.credential_definition_id = credential_definition_id + self.credential_definition = credential_definition + + +class CredDefStateSchema(BaseModelSchema): + """CredDefStateSchema.""" + + class Meta: + """CredDefStateSchema metadata.""" + + model_class = CredDefState + unknown = EXCLUDE + + state = fields.Str( + validate=OneOf( + [ + CredDefState.STATE_FINISHED, + CredDefState.STATE_FAILED, + CredDefState.STATE_ACTION, + CredDefState.STATE_WAIT, + ] + ) + ) + credential_definition_id = fields.Str(description="credential definition id") + credential_definition = fields.Nested( + CredDefSchema(), description="credential definition" + ) + + +class CredDefResult(BaseModel): + """Cred def result.""" + + class Meta: + """CredDefResult metadata.""" + + schema_class = "CredDefResultSchema" def __init__( self, - credential_definition: AnonCredsCredentialDefinition, - credential_definition_id: str, - resolution_metadata: dict, + job_id: Optional[str], + credential_definition_state: CredDefState, + registration_metadata: dict, credential_definition_metadata: dict, - **kwargs + **kwargs, ): super().__init__(**kwargs) - self.credential_definition = credential_definition - self.credential_definition_id = credential_definition_id - self.resolution_metadata = resolution_metadata + self.job_id = job_id + self.credential_definition_state = credential_definition_state + self.registration_metadata = registration_metadata self.credential_definition_metadata = credential_definition_metadata -class AnonCredsRegistryGetCredentialDefinitions(BaseModel): - """AnonCredsRegistryGetCredentialDefinitions""" +class CredDefResultSchema(BaseModelSchema): + """Cred def result schema.""" class Meta: - """AnonCredsRegistryGetCredentialDefinitions metadata.""" + """CredDefResultSchema metadata.""" - schema_class = "AnonCredsRegistryGetCredentialDefinitionsSchema" + model_class = CredDefResult + unknown = EXCLUDE - def __init__(self, credential_definition_ids: list, **kwargs): - super().__init__(**kwargs) - self.credential_definition_ids = credential_definition_ids + job_id = fields.Str() + credential_definition_state = fields.Nested(CredDefStateSchema()) + registration_metadata = fields.Dict() + # For indy, credential_definition_metadata will contain the seqNo + credential_definition_metadata = fields.Dict() -class AnonCredsRegistryGetCredentialDefinitionsSchema(BaseModelSchema): - """AnonCredsRegistryGetCredentialDefinitionsSchema""" +class GetCredDefResult(BaseModel): + """Get cred def result.""" class Meta: - """AnonCredsRegistryGetCredentialDefinitionsSchema metadata""" + """AnonCredsRegistryGetCredDef metadata.""" - model_class = AnonCredsRegistryGetCredentialDefinitions - unknown = EXCLUDE + schema_class = "GetCredDefResultSchema" - credential_definition_ids = fields.List( - fields.Str( - data_key="credentialDefinitionIds", - description="credential definition identifiers", - **INDY_CRED_DEF_ID, - ) - ) + def __init__( + self, + credential_definition_id: str, + credential_definition: CredDef, + resolution_metadata: dict, + credential_definition_metadata: dict, + **kwargs, + ): + super().__init__(**kwargs) + self.credential_definition_id = credential_definition_id + self.credential_definition = credential_definition + self.resolution_metadata = resolution_metadata + self.credential_definition_metadata = credential_definition_metadata -class AnonCredsRegistryGetCredentialDefinitionSchema(BaseModelSchema): - """Parameters and validators for credential definition list response.""" +class GetCredDefResultSchema(BaseModelSchema): + """GetCredDefResultSchema.""" class Meta: - """AnonCredsRegistryGetCredentialDefinitionSchema metadata.""" + """GetCredDefResultSchema metadata.""" - model_class = AnonCredsRegistryGetCredentialDefinition + model_class = GetCredDefResult unknown = EXCLUDE - credential_definition_id = fields.Str( - description="Credential definition identifier", - **INDY_CRED_DEF_ID, + credential_definition_id = fields.Str(description="credential definition id") + credential_definition = fields.Nested( + CredDefSchema(), description="credential definition" ) - credential_definition = fields.Nested(AnonCredsCredentialDefinitionSchema()) resolution_metadata = fields.Dict() - credential_definition_metadata = fields.Dict() + credential_definitions_metadata = fields.Dict() class AnonCredsRevocationRegistryDefinition(BaseModel): @@ -229,7 +338,7 @@ def __init__( max_cred_num: int, tails_Location: str, tails_hash: str, - **kwargs + **kwargs, ): super().__init__(**kwargs) self.issuer_id = issuer_id @@ -284,7 +393,7 @@ def __init__( revocation_registry_id: str, resolution_metadata: Dict[str, Any], revocation_registry_metadata: Dict[str, Any], - **kwargs + **kwargs, ): super().__init__(**kwargs) self.revocation_registry = revocation_registry @@ -352,7 +461,7 @@ def __init__( revocation_list: List[int], current_accumulator: str, timestamp: int, - **kwargs + **kwargs, ): super().__init__(**kwargs) self.issuer_id = issuer_id @@ -401,7 +510,7 @@ def __init__( revocation_list: AnonCredsRevocationList, resolution_metadata: Dict[str, Any], revocation_registry_metadata: Dict[str, Any], - **kwargs + **kwargs, ): super().__init__(**kwargs) self.revocation_list = revocation_list diff --git a/aries_cloudagent/anoncreds/models/anoncreds_schema.py b/aries_cloudagent/anoncreds/models/anoncreds_schema.py index d9c32c2a37..0acf806829 100644 --- a/aries_cloudagent/anoncreds/models/anoncreds_schema.py +++ b/aries_cloudagent/anoncreds/models/anoncreds_schema.py @@ -5,6 +5,8 @@ from marshmallow import EXCLUDE, fields from marshmallow.validate import OneOf +from anoncreds import Schema + from ...messaging.models.base import BaseModel, BaseModelSchema @@ -25,6 +27,15 @@ def __init__( self.name = name self.version = version + @classmethod + def from_native(cls, schema: Schema) -> "AnonCredsSchema": + """Convert from native object.""" + return cls.deserialize(schema.to_dict()) + + def to_native(self): + """Convert to native object.""" + return Schema.load(self.serialize()) + class AnonCredsSchemaSchema(BaseModelSchema): """Marshmallow schema for anoncreds schema.""" @@ -70,11 +81,19 @@ def __init__( **kwargs ): super().__init__(**kwargs) - self.schema_ = schema + self.schema_value = schema self.schema_id = schema_id self.resolution_metadata = resolution_metadata self.schema_metadata = schema_metadata + @property + def schema(self) -> AnonCredsSchema: + """Alias for schema_value. + + `schema` can't be used directly due to a limitation of marshmallow. + """ + return self.schema_value + class GetSchemaResultSchema(BaseModelSchema): """Parameters and validators for schema create query.""" @@ -85,7 +104,7 @@ class Meta: model_class = GetSchemaResult unknown = EXCLUDE - schema_ = fields.Nested(AnonCredsSchemaSchema(), data_key="schema") + schema_value = fields.Nested(AnonCredsSchemaSchema(), data_key="schema") schema_id = fields.Str(data_key="schemaId", description="Schema identifier") resolution_metadata = fields.Dict() schema_metadata = fields.Dict() @@ -104,14 +123,20 @@ class Meta: schema_class = "SchemaStateSchema" - def __init__( - self, state: str, schema_id: str, schema_def: AnonCredsSchema, **kwargs - ): + def __init__(self, state: str, schema_id: str, schema: AnonCredsSchema, **kwargs): """Initialize a new SchemaState.""" super().__init__(**kwargs) self.state = state self.schema_id = schema_id - self.schema_def = schema_def + self.schema_value = schema + + @property + def schema(self) -> AnonCredsSchema: + """Alias to schema_value. + + `schema` can't be used directly due to limitations of marshmallow. + """ + return self.schema_value class SchemaStateSchema(BaseModelSchema): @@ -133,7 +158,7 @@ class Meta: ) ) schema_id = fields.Str(description="Schema identifier") - schema_def = fields.Nested(AnonCredsSchemaSchema(), data_key="schema") + schema_value = fields.Nested(AnonCredsSchemaSchema(), data_key="schema") class SchemaResult(BaseModel): diff --git a/aries_cloudagent/ledger/base.py b/aries_cloudagent/ledger/base.py index d2cbfe817a..8a780dde98 100644 --- a/aries_cloudagent/ledger/base.py +++ b/aries_cloudagent/ledger/base.py @@ -502,6 +502,71 @@ async def create_and_send_credential_definition( return (credential_definition_id, json.loads(credential_definition_json), novel) + async def send_credential_definition( + self, + schema_id: str, + cred_def_id: str, + cred_def: dict, + write_ledger: bool = True, + endorser_did: str = None, + ) -> int: + """ + Send credential definition to ledger and store relevant key matter in wallet. + + Args: + issuer: The issuer instance to use for credential definition creation + schema_id: The schema id of the schema to create cred def for + signature_type: The signature type to use on the credential definition + tag: Optional tag to distinguish multiple credential definitions + support_revocation: Optional flag to enable revocation for this cred def + + Returns: + Tuple with cred def id, cred def structure, and whether it's novel + + """ + public_info = await self.get_wallet_public_did() + if not public_info: + raise BadLedgerRequestError( + "Cannot publish credential definition without a public DID" + ) + + schema = await self.get_schema(schema_id) + if not schema: + raise LedgerError(f"Ledger {self.pool.name} has no schema {schema_id}") + + # check if cred def is on ledger already + ledger_cred_def = await self.fetch_credential_definition(cred_def_id) + if ledger_cred_def: + credential_definition_json = json.dumps(ledger_cred_def) + raise LedgerObjectAlreadyExistsError( + f"Credential definition with id {cred_def_id} " + "already exists in wallet and on ledger.", + credential_definition_json, + ) + + if await self.is_ledger_read_only(): + raise LedgerError( + "Error cannot write cred def when ledger is in read only mode" + ) + + cred_def_req = await self._create_credential_definition_request( + public_info, + json.dumps(cred_def), + write_ledger=write_ledger, + endorser_did=endorser_did, + ) + + resp = await self.txn_submit( + cred_def_req, True, sign_did=public_info, write_ledger=write_ledger + ) + + # TODO Clean up + # if not write_ledger: + # return (credential_definition_id, {"signed_txn": resp}, novel) + + seq_no = json.loads(resp)["result"]["txnMetadata"]["seqNo"] + return seq_no + @abstractmethod async def _create_credential_definition_request( self, From ec4bac368fd2b1c1aa1f9888fa92630227bf78db Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Mon, 27 Mar 2023 20:21:39 -0400 Subject: [PATCH 079/150] feat: credential definitions completed Signed-off-by: Daniel Bluhm --- .../default/legacy_indy_registry/registry.py | 14 +++++++++++++- aries_cloudagent/anoncreds/anoncreds/routes.py | 2 +- aries_cloudagent/anoncreds/issuer.py | 6 ++---- .../anoncreds/models/anoncreds_cred_def.py | 5 ++++- 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py index 5d031a1a69..d65be15afc 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py @@ -265,13 +265,25 @@ async def register_credential_definition( "exists in wallet but not on the ledger" ) from err + # Translate anoncreds object to indy object + LOGGER.debug("Registering credential definition: %s", cred_def_id) + indy_cred_def = { + "id": cred_def_id, + "schemaId": str(schema.schema_metadata["seqNo"]), + "tag": credential_definition.tag, + "type": credential_definition.type, + "value": credential_definition.value.serialize(), + "ver": "1.0", + } + LOGGER.debug("Cred def value: %s", indy_cred_def) + try: async with ledger: seq_no = await shield( ledger.send_credential_definition( credential_definition.schema_id, cred_def_id, - credential_definition.serialize(), + indy_cred_def, write_ledger=True, endorser_did=credential_definition.issuer_id, ) diff --git a/aries_cloudagent/anoncreds/anoncreds/routes.py b/aries_cloudagent/anoncreds/anoncreds/routes.py index 119eb13756..876deb27fa 100644 --- a/aries_cloudagent/anoncreds/anoncreds/routes.py +++ b/aries_cloudagent/anoncreds/anoncreds/routes.py @@ -349,7 +349,7 @@ async def register(app: web.Application): allow_head=False, ), web.get( - "/anoncreds/credential-definitions/", + "/anoncreds/credential-definitions", cred_defs_get, allow_head=False, ), diff --git a/aries_cloudagent/anoncreds/issuer.py b/aries_cloudagent/anoncreds/issuer.py index 8b4efb94ca..2de2d3ac6b 100644 --- a/aries_cloudagent/anoncreds/issuer.py +++ b/aries_cloudagent/anoncreds/issuer.py @@ -3,11 +3,9 @@ from abc import ABC, ABCMeta, abstractmethod from typing import Optional, Sequence, Tuple -from aries_cloudagent.anoncreds.models.anoncreds_cred_def import CredDefResult - -from .models.anoncreds_schema import SchemaResult - from ..core.error import BaseError +from .models.anoncreds_cred_def import CredDefResult +from .models.anoncreds_schema import SchemaResult DEFAULT_CRED_DEF_TAG = "default" diff --git a/aries_cloudagent/anoncreds/models/anoncreds_cred_def.py b/aries_cloudagent/anoncreds/models/anoncreds_cred_def.py index 8fd65ef917..3b4112722b 100644 --- a/aries_cloudagent/anoncreds/models/anoncreds_cred_def.py +++ b/aries_cloudagent/anoncreds/models/anoncreds_cred_def.py @@ -111,7 +111,10 @@ class Meta: schema_class = "CredDefValueSchema" def __init__( - self, primary: CredDefValuePrimary, revocation: CredDefValueRevocation, **kwargs + self, + primary: CredDefValuePrimary, + revocation: Optional[CredDefValueRevocation] = None, + **kwargs, ): super().__init__(**kwargs) self.primary = primary From eb8d978bf671b91dea884c8a2a321a89fad4d22c Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Wed, 29 Mar 2023 11:04:31 -0400 Subject: [PATCH 080/150] feat: implement revocation registry methods in issuer Signed-off-by: Daniel Bluhm --- .../anoncreds/anoncreds/anoncreds_registry.py | 32 +- .../anoncreds/anoncreds/base_registry.py | 20 +- .../anoncreds/anoncreds/issuer.py | 339 +++++++++++------- .../anoncreds/models/anoncreds_cred_def.py | 254 +++++++++++-- aries_cloudagent/askar/profile.py | 4 +- aries_cloudagent/config/injection_context.py | 2 +- 6 files changed, 490 insertions(+), 161 deletions(-) diff --git a/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py b/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py index cc7d6f8fd0..b5190e0951 100644 --- a/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py @@ -10,6 +10,10 @@ CredDef, CredDefResult, GetCredDefResult, + RevRegDef, + RevRegDefResult, + RevStatusList, + RevStatusListResult, ) from ..models.anoncreds_schema import AnonCredsSchema, GetSchemaResult, SchemaResult from .base_registry import ( @@ -118,11 +122,19 @@ async def get_revocation_registry_definition( resolver = await self._resolver_for_identifier(revocation_registry_id) return await resolver.get_revocation_registry_definition(revocation_registry_id) - # TODO: determine keyword arguments - async def register_revocation_registry_definition(self): + async def register_revocation_registry_definition( + self, + profile: Profile, + revocation_registry_definition: RevRegDef, + options: Optional[dict] = None, + ) -> RevRegDefResult: """Register a revocation registry definition on the registry.""" - registrar = await self._registrar_for_identifier("something") - return await registrar.register_revocation_registry_definition() + registrar = await self._registrar_for_identifier( + revocation_registry_definition.issuer_id + ) + return await registrar.register_revocation_registry_definition( + profile, revocation_registry_definition, options + ) async def get_revocation_list( self, revocation_registry_id: str, timestamp: str @@ -131,8 +143,14 @@ async def get_revocation_list( resolver = await self._resolver_for_identifier(revocation_registry_id) return await resolver.get_revocation_list(revocation_registry_id, timestamp) - # TODO: determine keyword arguments - async def register_revocation_list(self): + async def register_revocation_status_list( + self, + profile: Profile, + rev_status_list: RevStatusList, + options: Optional[dict] = None, + ) -> RevStatusListResult: """Register a revocation list on the registry.""" registrar = await self._registrar_for_identifier("something") - return await registrar.register_revocation_registry_definition() + return await registrar.register_revocation_status_list( + profile, rev_status_list, options + ) diff --git a/aries_cloudagent/anoncreds/anoncreds/base_registry.py b/aries_cloudagent/anoncreds/anoncreds/base_registry.py index 00fb3352f4..ccd467a056 100644 --- a/aries_cloudagent/anoncreds/anoncreds/base_registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/base_registry.py @@ -11,6 +11,10 @@ CredDef, CredDefResult, GetCredDefResult, + RevRegDef, + RevRegDefResult, + RevStatusList, + RevStatusListResult, ) from ..models.anoncreds_schema import AnonCredsSchema, GetSchemaResult, SchemaResult @@ -130,12 +134,20 @@ async def register_credential_definition( ) -> CredDefResult: """Register a credential definition on the registry.""" - # TODO: determine keyword arguments @abstractmethod - async def register_revocation_registry_definition(self): + async def register_revocation_registry_definition( + self, + profile: Profile, + revocation_registry_definition: RevRegDef, + options: Optional[dict] = None, + ) -> RevRegDefResult: """Register a revocation registry definition on the registry.""" - # TODO: determine keyword arguments @abstractmethod - async def register_revocation_list(self): + async def register_revocation_status_list( + self, + profile: Profile, + rev_status_list: RevStatusList, + options: Optional[dict] = None, + ) -> RevStatusListResult: """Register a revocation list on the registry.""" diff --git a/aries_cloudagent/anoncreds/anoncreds/issuer.py b/aries_cloudagent/anoncreds/anoncreds/issuer.py index cf28c7703e..bd6c537f52 100644 --- a/aries_cloudagent/anoncreds/anoncreds/issuer.py +++ b/aries_cloudagent/anoncreds/anoncreds/issuer.py @@ -23,7 +23,15 @@ from .anoncreds_registry import AnonCredsRegistry from .base_registry import AnonCredsSchemaAlreadyExists -from ..models.anoncreds_cred_def import CredDef, CredDefResult, CredDefState +from ..models.anoncreds_cred_def import ( + CredDef, + CredDefResult, + CredDefState, + RevRegDef, + RevRegDefResult, + RevRegDefState, + RevStatusList, +) from ..models.anoncreds_schema import AnonCredsSchema, SchemaResult, SchemaState from ...askar.profile import AskarProfile @@ -67,6 +75,29 @@ def profile(self) -> AskarProfile: """Accessor for the profile instance.""" return self._profile + async def _update_entry_state(self, category: str, name: str, state: str): + """Update the state tag of an entry in a given category.""" + try: + async with self._profile.transaction() as txn: + entry = await txn.handle.fetch( + category, + name, + for_update=True, + ) + if not entry: + raise AnonCredsIssuerError( + f"{category} with id {name} could not be found" + ) + + entry.tags["state"] = state + await txn.handle.replace( + CATEGORY_SCHEMA, + name, + tags=entry.tags, + ) + except AskarError as err: + raise AnonCredsIssuerError(f"Error marking {category} as {state}") from err + async def _store_schema( self, schema_id: str, @@ -162,22 +193,13 @@ async def create_and_register_schema( except AnoncredsError as err: raise AnonCredsIssuerError("Error creating schema") from err + async def update_schema_state(self, schema_id: str, state: str): + """Update the state of the stored schema.""" + await self._update_entry_state(CATEGORY_SCHEMA, schema_id, state) + async def finish_schema(self, schema_id: str): - try: - async with self._profile.transaction() as txn: - entry = await txn.handle.fetch( - CATEGORY_SCHEMA, - schema_id, - for_update=True, - ) - entry.tags["state"] = SchemaState.STATE_FINISHED - await txn.handle.replace( - CATEGORY_SCHEMA, - schema_id, - tags=entry.tags, - ) - except AskarError as err: - raise AnonCredsIssuerError("Error marking schema as finished") from err + """Mark a schema as finished.""" + await self.update_schema_state(schema_id, SchemaState.STATE_FINISHED) async def get_created_schemas( self, @@ -314,25 +336,13 @@ async def create_and_store_credential_definition( return result - async def finish_credential_definition(self, cred_def_id: str): + async def update_cred_def_state(self, cred_def_id: str, state: str): + """Update the state of a cred def.""" + await self._update_entry_state(CATEGORY_CRED_DEF, cred_def_id, state) + + async def finish_cred_def(self, cred_def_id: str): """Finish a cred def.""" - try: - async with self._profile.transaction() as txn: - entry = await txn.handle.fetch( - CATEGORY_CRED_DEF, - cred_def_id, - for_update=True, - ) - entry.tags["state"] = CredDefState.STATE_FINISHED - await txn.handle.replace( - CATEGORY_SCHEMA, - cred_def_id, - tags=entry.tags, - ) - except AskarError as err: - raise AnonCredsIssuerError( - "Error marking credential definition as finished" - ) from err + await self.update_cred_def_state(cred_def_id, CredDefState.STATE_FINISHED) async def get_created_credential_definitions( self, @@ -362,6 +372,174 @@ async def get_created_credential_definitions( # entry.name was stored as the credential_definition's ID return [entry.name for entry in credential_definitions] + async def create_and_register_revocation_registry_definition( + self, + issuer_id: str, + cred_def_id: str, + registry_type: str, + tag: str, + max_cred_num: int, + tails_base_path: str, + options: Optional[dict] = None, + ) -> RevRegDefResult: + """ + Create a new revocation registry and store it in the wallet. + + Args: + origin_did: the DID issuing the revocation registry + cred_def_id: the identifier of the related credential definition + revoc_def_type: the revocation registry type (default CL_ACCUM) + tag: the unique revocation registry tag + max_cred_num: the number of credentials supported in the registry + tails_base_path: where to store the tails file + issuance_type: optionally override the issuance type + + Returns: + A tuple of the revocation registry ID, JSON, and entry JSON + + """ + try: + async with self._profile.session() as session: + cred_def = await session.handle.fetch(CATEGORY_CRED_DEF, cred_def_id) + except AskarError as err: + raise AnonCredsIssuerError( + "Error retrieving credential definition" + ) from err + + if not cred_def: + raise AnonCredsIssuerError( + "Credential definition not found for revocation registry" + ) + + try: + ( + rev_reg_def, + rev_reg_def_private, + ) = await asyncio.get_event_loop().run_in_executor( + None, + lambda: RevocationRegistryDefinition.create( + cred_def_id, + cred_def.raw_value, + issuer_id, + tag, + registry_type, + max_cred_num, + tails_dir_path=tails_base_path, + ), + ) + + except AnoncredsError as err: + raise AnonCredsIssuerError("Error creating revocation registry") from err + + rev_reg_def_json = rev_reg_def.to_json() + + anoncreds_registry = self.profile.inject(AnonCredsRegistry) + result = await anoncreds_registry.register_revocation_registry_definition( + self.profile, RevRegDef.from_native(rev_reg_def), options + ) + + # rev_reg_def_id = f"{origin_did}:4:{cred_def_id}:CL_ACCUM:{tag}" + rev_reg_def_id = result.rev_reg_def_id + + try: + async with self._profile.transaction() as txn: + await txn.handle.insert( + CATEGORY_REV_REG_INFO, + rev_reg_def_id, + value_json={"curr_id": 0, "used_ids": []}, + ) + await txn.handle.insert( + CATEGORY_REV_REG_DEF, + rev_reg_def_id, + rev_reg_def_json, + tags={"cred_def_id": cred_def_id}, + ) + await txn.handle.insert( + CATEGORY_REV_REG_DEF_PRIVATE, + rev_reg_def_id, + rev_reg_def_private.to_json_buffer(), + ) + await txn.commit() + except AskarError as err: + raise AnonCredsIssuerError("Error saving new revocation registry") from err + + return result + + async def update_revocation_registry_definition_state( + self, rev_reg_def_id: str, state: str + ): + """Update the state of a rev reg def.""" + await self._update_entry_state(CATEGORY_REV_REG_DEF, rev_reg_def_id, state) + + async def finish_revocation_registry_definition(self, rev_reg_def_id: str): + """Mark a rev reg def as finished.""" + await self.update_revocation_registry_definition_state( + rev_reg_def_id, RevRegDefState.STATE_FINISHED + ) + + async def get_created_revocation_registry_definitions( + self, + cred_def_id: Optional[str] = None, + state: Optional[str] = None, + ) -> Sequence[str]: + """Retrieve IDs of rev reg defs previously created.""" + async with self._profile.session() as session: + # TODO limit? scan? + rev_reg_defs = await session.handle.fetch_all( + CATEGORY_REV_REG_DEF, + { + key: value + for key, value in { + "cred_def_id": cred_def_id, + "state": state, + }.items() + if value is not None + }, + ) + # entry.name was stored as the credential_definition's ID + return [entry.name for entry in rev_reg_defs] + + async def create_and_register_revocation_status_list( + self, rev_reg_def_id: str, timestamp: int, options: dict + ): + """Create and register a revocation status list.""" + try: + async with self._profile.session() as session: + rev_reg_def_entry = await session.handle.fetch( + CATEGORY_REV_REG_DEF, rev_reg_def_id + ) + except AskarError as err: + raise AnonCredsIssuerError( + "Error retrieving credential definition" + ) from err + + if not rev_reg_def_entry: + raise AnonCredsIssuerError( + f"Revocation registry definition not found for id {rev_reg_def_id}" + ) + + issuer_id = rev_reg_def_entry.value_json["issuerId"] + + rev_status_list = RevocationStatusList.create( + rev_reg_def_id, rev_reg_def_entry.raw_value, issuer_id, timestamp + ) + + anoncreds_registry = self.profile.inject(AnonCredsRegistry) + result = await anoncreds_registry.register_revocation_status_list( + self.profile, RevStatusList.from_native(rev_status_list), options + ) + rev_status_list_json = rev_status_list.to_json() + + try: + async with self._profile.session() as session: + await session.handle.insert( + CATEGORY_REV_STATUS_LIST, rev_reg_def_id, rev_status_list_json + ) + except AskarError as err: + raise AnonCredsIssuerError("Error saving new revocation registry") from err + + return result + async def create_credential_offer(self, credential_definition_id: str) -> str: """ Create a credential offer for the given credential definition id. @@ -737,96 +915,3 @@ def update(d1, d2): return await asyncio.get_event_loop().run_in_executor( None, update, fro_delta, to_delta ) - - async def create_and_store_revocation_registry( - self, - origin_did: str, - cred_def_id: str, - revoc_def_type: str, - tag: str, - max_cred_num: int, - tails_base_path: str, - ) -> Tuple[str, str, str]: - """ - Create a new revocation registry and store it in the wallet. - - Args: - origin_did: the DID issuing the revocation registry - cred_def_id: the identifier of the related credential definition - revoc_def_type: the revocation registry type (default CL_ACCUM) - tag: the unique revocation registry tag - max_cred_num: the number of credentials supported in the registry - tails_base_path: where to store the tails file - issuance_type: optionally override the issuance type - - Returns: - A tuple of the revocation registry ID, JSON, and entry JSON - - """ - # TODO Passed in by caller? - rev_reg_def_id = f"{origin_did}:4:{cred_def_id}:CL_ACCUM:{tag}" - try: - async with self._profile.session() as session: - cred_def = await session.handle.fetch(CATEGORY_CRED_DEF, cred_def_id) - except AskarError as err: - raise AnonCredsIssuerError( - "Error retrieving credential definition" - ) from err - if not cred_def: - raise AnonCredsIssuerError( - "Credential definition not found for revocation registry" - ) - - try: - ( - rev_reg_def, - rev_reg_def_private, - ) = await asyncio.get_event_loop().run_in_executor( - None, - lambda: RevocationRegistryDefinition.create( - cred_def_id, - cred_def.raw_value, - origin_did, - tag, - revoc_def_type, - max_cred_num, - tails_dir_path=tails_base_path, - ), - ) - - rev_status_list = RevocationStatusList.create( - rev_reg_def_id, rev_reg_def, origin_did, int(time.time()) - ) - except AnoncredsError as err: - raise AnonCredsIssuerError("Error creating revocation registry") from err - - rev_reg_def_json = rev_reg_def.to_json() - rev_status_list_json = rev_status_list.to_json() - - try: - async with self._profile.transaction() as txn: - await txn.handle.insert( - CATEGORY_REV_STATUS_LIST, rev_reg_def_id, rev_status_list_json - ) - await txn.handle.insert( - CATEGORY_REV_REG_INFO, - rev_reg_def_id, - value_json={"curr_id": 0, "used_ids": []}, - ) - await txn.handle.insert( - CATEGORY_REV_REG_DEF, rev_reg_def_id, rev_reg_def_json - ) - await txn.handle.insert( - CATEGORY_REV_REG_DEF_PRIVATE, - rev_reg_def_id, - rev_reg_def_private.to_json_buffer(), - ) - await txn.commit() - except AskarError as err: - raise AnonCredsIssuerError("Error saving new revocation registry") from err - - return ( - rev_reg_def_id, - rev_reg_def_json, - rev_status_list_json, - ) diff --git a/aries_cloudagent/anoncreds/models/anoncreds_cred_def.py b/aries_cloudagent/anoncreds/models/anoncreds_cred_def.py index 3b4112722b..4fc218ef8d 100644 --- a/aries_cloudagent/anoncreds/models/anoncreds_cred_def.py +++ b/aries_cloudagent/anoncreds/models/anoncreds_cred_def.py @@ -1,8 +1,12 @@ """Anoncreds cred def OpenAPI validators""" from typing import Any, Dict, List, Optional from typing_extensions import Literal -from anoncreds import CredentialDefinition +from anoncreds import ( + CredentialDefinition, + RevocationRegistryDefinition, + RevocationStatusList, +) from marshmallow import EXCLUDE, fields from marshmallow.validate import OneOf @@ -322,13 +326,13 @@ class Meta: credential_definitions_metadata = fields.Dict() -class AnonCredsRevocationRegistryDefinition(BaseModel): - """AnonCredsRevocationRegistryDefinition""" +class RevRegDef(BaseModel): + """RevRegDef""" class Meta: - """AnonCredsRevocationRegistryDefinition metadata.""" + """RevRegDef metadata.""" - schema_class = "AnonCredsRevocationRegistryDefinitionSchema" + schema_class = "RevRegDefSchema" def __init__( self, @@ -353,14 +357,23 @@ def __init__( self.tails_location = tails_Location self.tails_hash = tails_hash + @classmethod + def from_native(cls, rev_reg_def: RevocationRegistryDefinition): + """Convert a native revocation registry definition to a RevRegDef object.""" + return cls.deserialize(rev_reg_def.to_json()) + + def to_native(self): + """Convert to native anoncreds revocation registry definition.""" + return RevocationRegistryDefinition.load(self.serialize()) + -class AnonCredsRevocationRegistryDefinitionSchema(BaseModelSchema): - """AnonCredsRevocationRegistryDefinitionSchema""" +class RevRegDefSchema(BaseModelSchema): + """RevRegDefSchema.""" class Meta: - """AnonCredsRevocationRegistryDefinitionSchema metadata.""" + """RevRegDefSchema metadata.""" - model_class = AnonCredsRevocationRegistryDefinition + model_class = RevRegDef unknown = EXCLUDE issuer_id = fields.Str( @@ -382,6 +395,104 @@ class Meta: tails_hash = fields.Str(data_key="tailsHash") +class RevRegDefState(BaseModel): + """RevRegDefState.""" + + STATE_FINISHED = "finished" + STATE_FAILED = "failed" + STATE_ACTION = "action" + STATE_WAIT = "wait" + + class Meta: + """RevRegDefState metadata.""" + + schema_class = "RevRegDefStateSchema" + + def __init__( + self, + state: str, + revocation_registry_definition_id: str, + revocation_registry_definition: RevRegDef, + ): + self.state = state + self.revocation_registry_definition_id = revocation_registry_definition_id + self.revocation_registry_definition = revocation_registry_definition + + +class RevRegDefStateSchema(BaseModelSchema): + """RevRegDefStateSchema.""" + + class Meta: + """RevRegDefStateSchema metadata.""" + + model_class = RevRegDefState + unknown = EXCLUDE + + state = fields.Str( + validate=OneOf( + [ + RevRegDefState.STATE_FINISHED, + RevRegDefState.STATE_FAILED, + RevRegDefState.STATE_ACTION, + RevRegDefState.STATE_WAIT, + ] + ) + ) + revocation_registry_definition_id = fields.Str( + description="revocation registry definition id" + ) + revocation_registry_definition = fields.Nested( + RevRegDefSchema(), description="revocation registry definition" + ) + + +class RevRegDefResult(BaseModel): + """Cred def result.""" + + class Meta: + """RevRegDefResult metadata.""" + + schema_class = "RevRegDefResultSchema" + + def __init__( + self, + job_id: Optional[str], + revocation_registry_definition_state: RevRegDefState, + registration_metadata: dict, + revocation_registry_definition_metadata: dict, + **kwargs, + ): + super().__init__(**kwargs) + self.job_id = job_id + self.revocation_registry_definition_state = revocation_registry_definition_state + self.registration_metadata = registration_metadata + self.revocation_registry_definition_metadata = ( + revocation_registry_definition_metadata + ) + + @property + def rev_reg_def_id(self): + return ( + self.revocation_registry_definition_state.revocation_registry_definition_id + ) + + +class RevRegDefResultSchema(BaseModelSchema): + """Cred def result schema.""" + + class Meta: + """RevRegDefResultSchema metadata.""" + + model_class = RevRegDefResult + unknown = EXCLUDE + + job_id = fields.Str() + revocation_registry_definition_state = fields.Nested(RevRegDefStateSchema()) + registration_metadata = fields.Dict() + # For indy, revocation_registry_definition_metadata will contain the seqNo + revocation_registry_definition_metadata = fields.Dict() + + class AnonCredsRegistryGetRevocationRegistryDefinition(BaseModel): """AnonCredsRegistryGetRevocationRegistryDefinition""" @@ -392,7 +503,7 @@ class Meta: def __init__( self, - revocation_registry: AnonCredsRevocationRegistryDefinition, + revocation_registry: RevRegDef, revocation_registry_id: str, resolution_metadata: Dict[str, Any], revocation_registry_metadata: Dict[str, Any], @@ -412,7 +523,7 @@ class Meta: model_class = AnonCredsRegistryGetRevocationRegistryDefinition unknown = EXCLUDE - revocation_registry = fields.Nested(AnonCredsRevocationRegistryDefinitionSchema()) + revocation_registry = fields.Nested(RevRegDefSchema()) revocation_registry_id = fields.Str() resolution_metadata = fields.Dict() revocation_registry_metadata = fields.Dict() @@ -449,11 +560,11 @@ class Meta: ) -class AnonCredsRevocationList(BaseModel): - """AnonCredsRevocationList""" +class RevStatusList(BaseModel): + """RevStatusList.""" class Meta: - """AnonCredsRevocationList metadata.""" + """RevStatusList metadata.""" schema_class = "AnonCredsRevocationListSchema" @@ -473,14 +584,23 @@ def __init__( self.current_accumulator = current_accumulator self.timestamp = timestamp + @classmethod + def from_native(cls, rev_status_list: RevocationStatusList): + """Convert from native revocation status list.""" + return cls.deserialize(rev_status_list.to_json()) + + def to_native(self): + """Convert to native revocation status list.""" + return RevocationStatusList.load(self.serialize()) + -class AnonCredsRevocationListSchema(BaseModelSchema): - """AnonCredsRevocationListSchema""" +class RevStatusListSchema(BaseModelSchema): + """RevStatusListSchema.""" class Meta: - """AnonCredsRevocationListSchema metadata.""" + """RevStatusListSchema metadata.""" - model_class = AnonCredsRevocationList + model_class = RevStatusList unknown = EXCLUDE issuer_id = fields.Str( @@ -500,6 +620,100 @@ class Meta: timestamp = fields.Int() +class RevStatusListState(BaseModel): + """RevStatusListState.""" + + STATE_FINISHED = "finished" + STATE_FAILED = "failed" + STATE_ACTION = "action" + STATE_WAIT = "wait" + + class Meta: + """RevStatusListState metadata.""" + + schema_class = "RevStatusListStateSchema" + + def __init__( + self, + state: str, + revocation_status_list_id: str, + revocation_status_list: RevStatusList, + ): + self.state = state + self.revocation_status_list_id = revocation_status_list_id + self.revocation_status_list = revocation_status_list + + +class RevStatusListStateSchema(BaseModelSchema): + """RevStatusListStateSchema.""" + + class Meta: + """RevStatusListStateSchema metadata.""" + + model_class = RevStatusListState + unknown = EXCLUDE + + state = fields.Str( + validate=OneOf( + [ + RevStatusListState.STATE_FINISHED, + RevStatusListState.STATE_FAILED, + RevStatusListState.STATE_ACTION, + RevStatusListState.STATE_WAIT, + ] + ) + ) + revocation_status_list_id = fields.Str( + description="revocation registry definition id" + ) + revocation_status_list = fields.Nested( + RevStatusListSchema(), description="revocation registry definition" + ) + + +class RevStatusListResult(BaseModel): + """Cred def result.""" + + class Meta: + """RevStatusListResult metadata.""" + + schema_class = "RevStatusListResultSchema" + + def __init__( + self, + job_id: Optional[str], + revocation_status_list_state: RevStatusListState, + registration_metadata: dict, + revocation_status_list_metadata: dict, + **kwargs, + ): + super().__init__(**kwargs) + self.job_id = job_id + self.revocation_status_list_state = revocation_status_list_state + self.registration_metadata = registration_metadata + self.revocation_status_list_metadata = revocation_status_list_metadata + + @property + def rev_reg_def_id(self): + return self.revocation_status_list_state.revocation_status_list_id + + +class RevStatusListResultSchema(BaseModelSchema): + """Cred def result schema.""" + + class Meta: + """RevStatusListResultSchema metadata.""" + + model_class = RevStatusListResult + unknown = EXCLUDE + + job_id = fields.Str() + revocation_status_list_state = fields.Nested(RevStatusListStateSchema()) + registration_metadata = fields.Dict() + # For indy, revocation_status_list_metadata will contain the seqNo + revocation_status_list_metadata = fields.Dict() + + class AnonCredsRegistryGetRevocationList(BaseModel): """AnonCredsRegistryGetRevocationList""" @@ -510,7 +724,7 @@ class Meta: def __init__( self, - revocation_list: AnonCredsRevocationList, + revocation_list: RevStatusList, resolution_metadata: Dict[str, Any], revocation_registry_metadata: Dict[str, Any], **kwargs, @@ -530,6 +744,6 @@ class Meta: model_class = AnonCredsRegistryGetRevocationList unknown = EXCLUDE - revocation_list = fields.Nested(AnonCredsRevocationListSchema) + revocation_list = fields.Nested(RevStatusListSchema) resolution_metadata = fields.Str() revocation_registry_metadata = fields.Dict() diff --git a/aries_cloudagent/askar/profile.py b/aries_cloudagent/askar/profile.py index b847dd294f..cbd63a3aa0 100644 --- a/aries_cloudagent/askar/profile.py +++ b/aries_cloudagent/askar/profile.py @@ -135,11 +135,11 @@ def bind_providers(self): ), ) - def session(self, context: InjectionContext = None) -> ProfileSession: + def session(self, context: InjectionContext = None) -> "AskarProfileSession": """Start a new interactive session with no transaction support requested.""" return AskarProfileSession(self, False, context=context) - def transaction(self, context: InjectionContext = None) -> ProfileSession: + def transaction(self, context: InjectionContext = None) -> "AskarProfileSession": """ Start a new interactive session with commit and rollback support. diff --git a/aries_cloudagent/config/injection_context.py b/aries_cloudagent/config/injection_context.py index 9e5c5e051b..09279ad7b8 100644 --- a/aries_cloudagent/config/injection_context.py +++ b/aries_cloudagent/config/injection_context.py @@ -64,7 +64,7 @@ def update_settings(self, settings: Mapping[str, object]): self.injector.settings.update(settings) def start_scope( - self, scope_name: str, settings: Mapping[str, object] = None + self, scope_name: str, settings: Optional[Mapping[str, object]] = None ) -> "InjectionContext": """Begin a new named scope. From faa53f9fc706f500ec630c10bb0e542a181ae6e9 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Wed, 29 Mar 2023 11:57:06 -0400 Subject: [PATCH 081/150] refactor: move anoncreds revocation models Signed-off-by: Daniel Bluhm --- .../anoncreds/anoncreds/anoncreds_registry.py | 6 +- .../anoncreds/anoncreds/base_registry.py | 6 +- .../default/did_indy_registry/registry.py | 2 + .../default/did_web_registry/registry.py | 2 + .../default/legacy_indy_registry/registry.py | 2 + .../anoncreds/anoncreds/issuer.py | 2 + .../anoncreds/models/anoncreds_cred_def.py | 439 +----------------- .../anoncreds/models/anoncreds_revocation.py | 427 +++++++++++++++++ 8 files changed, 447 insertions(+), 439 deletions(-) create mode 100644 aries_cloudagent/anoncreds/models/anoncreds_revocation.py diff --git a/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py b/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py index b5190e0951..f0d9f5f0a3 100644 --- a/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py @@ -5,11 +5,13 @@ from ...core.profile import Profile from ..models.anoncreds_cred_def import ( - AnonCredsRegistryGetRevocationList, - AnonCredsRegistryGetRevocationRegistryDefinition, CredDef, CredDefResult, GetCredDefResult, +) +from ..models.anoncreds_revocation import ( + AnonCredsRegistryGetRevocationList, + AnonCredsRegistryGetRevocationRegistryDefinition, RevRegDef, RevRegDefResult, RevStatusList, diff --git a/aries_cloudagent/anoncreds/anoncreds/base_registry.py b/aries_cloudagent/anoncreds/anoncreds/base_registry.py index ccd467a056..cf3a0248eb 100644 --- a/aries_cloudagent/anoncreds/anoncreds/base_registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/base_registry.py @@ -6,11 +6,13 @@ from ...core.error import BaseError from ...core.profile import Profile from ..models.anoncreds_cred_def import ( - AnonCredsRegistryGetRevocationList, - AnonCredsRegistryGetRevocationRegistryDefinition, CredDef, CredDefResult, GetCredDefResult, +) +from ..models.anoncreds_revocation import ( + AnonCredsRegistryGetRevocationList, + AnonCredsRegistryGetRevocationRegistryDefinition, RevRegDef, RevRegDefResult, RevStatusList, diff --git a/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py index d3797e7ee5..f79a27328a 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py @@ -7,6 +7,8 @@ from .....core.profile import Profile from ....models.anoncreds_cred_def import ( GetCredDefResult, +) +from ..models.anoncreds_revocation import ( AnonCredsRegistryGetRevocationList, AnonCredsRegistryGetRevocationRegistryDefinition, ) diff --git a/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py index 7c0536ccbe..f3fa4773e3 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py @@ -6,6 +6,8 @@ from .....core.profile import Profile from ....models.anoncreds_cred_def import ( GetCredDefResult, +) +from ..models.anoncreds_revocation import ( AnonCredsRegistryGetRevocationList, AnonCredsRegistryGetRevocationRegistryDefinition, ) diff --git a/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py index d65be15afc..68e7447332 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py @@ -24,6 +24,8 @@ CredDefValue, CredDefResult, GetCredDefResult, +) +from ..models.anoncreds_revocation import ( AnonCredsRegistryGetRevocationList, AnonCredsRegistryGetRevocationRegistryDefinition, ) diff --git a/aries_cloudagent/anoncreds/anoncreds/issuer.py b/aries_cloudagent/anoncreds/anoncreds/issuer.py index bd6c537f52..bdddda29f2 100644 --- a/aries_cloudagent/anoncreds/anoncreds/issuer.py +++ b/aries_cloudagent/anoncreds/anoncreds/issuer.py @@ -27,6 +27,8 @@ CredDef, CredDefResult, CredDefState, +) +from ..models.anoncreds_revocation import ( RevRegDef, RevRegDefResult, RevRegDefState, diff --git a/aries_cloudagent/anoncreds/models/anoncreds_cred_def.py b/aries_cloudagent/anoncreds/models/anoncreds_cred_def.py index 4fc218ef8d..7385b6d6f7 100644 --- a/aries_cloudagent/anoncreds/models/anoncreds_cred_def.py +++ b/aries_cloudagent/anoncreds/models/anoncreds_cred_def.py @@ -1,21 +1,13 @@ """Anoncreds cred def OpenAPI validators""" -from typing import Any, Dict, List, Optional +from typing import Optional from typing_extensions import Literal -from anoncreds import ( - CredentialDefinition, - RevocationRegistryDefinition, - RevocationStatusList, -) +from anoncreds import CredentialDefinition from marshmallow import EXCLUDE, fields from marshmallow.validate import OneOf -from aries_cloudagent.messaging.models.base import BaseModel, BaseModelSchema -from aries_cloudagent.messaging.valid import ( - GENERIC_DID, - INDY_CRED_DEF_ID, - NUM_STR_WHOLE, -) +from ...messaging.models.base import BaseModel, BaseModelSchema +from ...messaging.valid import NUM_STR_WHOLE class CredDefValuePrimary(BaseModel): @@ -324,426 +316,3 @@ class Meta: ) resolution_metadata = fields.Dict() credential_definitions_metadata = fields.Dict() - - -class RevRegDef(BaseModel): - """RevRegDef""" - - class Meta: - """RevRegDef metadata.""" - - schema_class = "RevRegDefSchema" - - def __init__( - self, - issuer_id: str, - type: Literal["CL_ACCUM"], - cred_def_id: str, - tag: str, - # TODO: determine type for `publicKeys` - public_keys: Any, - max_cred_num: int, - tails_Location: str, - tails_hash: str, - **kwargs, - ): - super().__init__(**kwargs) - self.issuer_id = issuer_id - self.type = type - self.cred_def_id = cred_def_id - self.tag = tag - self.public_keys = public_keys - self.max_cred_num = max_cred_num - self.tails_location = tails_Location - self.tails_hash = tails_hash - - @classmethod - def from_native(cls, rev_reg_def: RevocationRegistryDefinition): - """Convert a native revocation registry definition to a RevRegDef object.""" - return cls.deserialize(rev_reg_def.to_json()) - - def to_native(self): - """Convert to native anoncreds revocation registry definition.""" - return RevocationRegistryDefinition.load(self.serialize()) - - -class RevRegDefSchema(BaseModelSchema): - """RevRegDefSchema.""" - - class Meta: - """RevRegDefSchema metadata.""" - - model_class = RevRegDef - unknown = EXCLUDE - - issuer_id = fields.Str( - description="Issuer Identifier of the credential definition or schema", - **GENERIC_DID, - data_key="issuerId", - ) # TODO: get correct validator - type = fields.Str() - cred_def_id = fields.Str( - description="Credential definition identifier", - **INDY_CRED_DEF_ID, - data_key="credDefId", - ) - tag = fields.Str(description="""""") - # TODO: type for public key - public_keys = fields.Str(data_key="publicKeys") - max_cred_num = fields.Int(data_key="maxCredNum") - tails_location = fields.Str(data_key="tailsLocation") - tails_hash = fields.Str(data_key="tailsHash") - - -class RevRegDefState(BaseModel): - """RevRegDefState.""" - - STATE_FINISHED = "finished" - STATE_FAILED = "failed" - STATE_ACTION = "action" - STATE_WAIT = "wait" - - class Meta: - """RevRegDefState metadata.""" - - schema_class = "RevRegDefStateSchema" - - def __init__( - self, - state: str, - revocation_registry_definition_id: str, - revocation_registry_definition: RevRegDef, - ): - self.state = state - self.revocation_registry_definition_id = revocation_registry_definition_id - self.revocation_registry_definition = revocation_registry_definition - - -class RevRegDefStateSchema(BaseModelSchema): - """RevRegDefStateSchema.""" - - class Meta: - """RevRegDefStateSchema metadata.""" - - model_class = RevRegDefState - unknown = EXCLUDE - - state = fields.Str( - validate=OneOf( - [ - RevRegDefState.STATE_FINISHED, - RevRegDefState.STATE_FAILED, - RevRegDefState.STATE_ACTION, - RevRegDefState.STATE_WAIT, - ] - ) - ) - revocation_registry_definition_id = fields.Str( - description="revocation registry definition id" - ) - revocation_registry_definition = fields.Nested( - RevRegDefSchema(), description="revocation registry definition" - ) - - -class RevRegDefResult(BaseModel): - """Cred def result.""" - - class Meta: - """RevRegDefResult metadata.""" - - schema_class = "RevRegDefResultSchema" - - def __init__( - self, - job_id: Optional[str], - revocation_registry_definition_state: RevRegDefState, - registration_metadata: dict, - revocation_registry_definition_metadata: dict, - **kwargs, - ): - super().__init__(**kwargs) - self.job_id = job_id - self.revocation_registry_definition_state = revocation_registry_definition_state - self.registration_metadata = registration_metadata - self.revocation_registry_definition_metadata = ( - revocation_registry_definition_metadata - ) - - @property - def rev_reg_def_id(self): - return ( - self.revocation_registry_definition_state.revocation_registry_definition_id - ) - - -class RevRegDefResultSchema(BaseModelSchema): - """Cred def result schema.""" - - class Meta: - """RevRegDefResultSchema metadata.""" - - model_class = RevRegDefResult - unknown = EXCLUDE - - job_id = fields.Str() - revocation_registry_definition_state = fields.Nested(RevRegDefStateSchema()) - registration_metadata = fields.Dict() - # For indy, revocation_registry_definition_metadata will contain the seqNo - revocation_registry_definition_metadata = fields.Dict() - - -class AnonCredsRegistryGetRevocationRegistryDefinition(BaseModel): - """AnonCredsRegistryGetRevocationRegistryDefinition""" - - class Meta: - """AnonCredsRegistryGetRevocationRegistryDefinition metadata.""" - - schema_class = "AnonCredsRegistryGetRevocationRegistryDefinitionSchema" - - def __init__( - self, - revocation_registry: RevRegDef, - revocation_registry_id: str, - resolution_metadata: Dict[str, Any], - revocation_registry_metadata: Dict[str, Any], - **kwargs, - ): - super().__init__(**kwargs) - self.revocation_registry = revocation_registry - self.revocation_registry_id = revocation_registry_id - self.resolution_metadata = resolution_metadata - self.revocation_registry_metadata = revocation_registry_metadata - - -class AnonCredsRegistryGetRevocationRegistryDefinitionSchema(BaseModelSchema): - class Meta: - """AnonCredsRegistryGetRevocationRegistryDefinitionSchema metadata.""" - - model_class = AnonCredsRegistryGetRevocationRegistryDefinition - unknown = EXCLUDE - - revocation_registry = fields.Nested(RevRegDefSchema()) - revocation_registry_id = fields.Str() - resolution_metadata = fields.Dict() - revocation_registry_metadata = fields.Dict() - - -class AnonCredsRegistryGetRevocationRegistryDefinitions(BaseModel): - """AnonCredsRegistryGetRevocationRegistryDefinitions""" - - class Meta: - """AnonCredsRegistryGetRevocationRegistryDefinitions metadata.""" - - schema_class = "AnonCredsRegistryGetRevocationRegistryDefinitionsSchema" - - def __init__(self, revocation_definition_ids: list, **kwargs): - super().__init__(**kwargs) - self.revocation_definition_ids = revocation_definition_ids - - -class AnonCredsRegistryGetRevocationRegistryDefinitionsSchema(BaseModelSchema): - """AnonCredsRegistryGetRevocationRegistryDefinitionsSchema""" - - class Meta: - """AnonCredsRegistryGetRevocationRegistryDefinitionsSchema metadata""" - - model_class = AnonCredsRegistryGetRevocationRegistryDefinitions - unknown = EXCLUDE - - revocation_definition_ids = fields.List( - fields.Str( - data_key="revocation_definition_ids", - description="credential definition identifiers", - **INDY_CRED_DEF_ID, - ) - ) - - -class RevStatusList(BaseModel): - """RevStatusList.""" - - class Meta: - """RevStatusList metadata.""" - - schema_class = "AnonCredsRevocationListSchema" - - def __init__( - self, - issuer_id: str, - rev_reg_id: str, - revocation_list: List[int], - current_accumulator: str, - timestamp: int, - **kwargs, - ): - super().__init__(**kwargs) - self.issuer_id = issuer_id - self.rev_reg_id = rev_reg_id - self.revocation_list = revocation_list - self.current_accumulator = current_accumulator - self.timestamp = timestamp - - @classmethod - def from_native(cls, rev_status_list: RevocationStatusList): - """Convert from native revocation status list.""" - return cls.deserialize(rev_status_list.to_json()) - - def to_native(self): - """Convert to native revocation status list.""" - return RevocationStatusList.load(self.serialize()) - - -class RevStatusListSchema(BaseModelSchema): - """RevStatusListSchema.""" - - class Meta: - """RevStatusListSchema metadata.""" - - model_class = RevStatusList - unknown = EXCLUDE - - issuer_id = fields.Str( - description="Issuer Identifier of the credential definition or schema", - **GENERIC_DID, - data_key="issuerId", - ) # TODO: get correct validator - rev_reg_id = fields.Str( - description="", - **GENERIC_DID, - data_key="revRegId", - ) # TODO: get correct validator - revocation_list = fields.List( - fields.Str(description=""), description="", data_key="revocationList" - ) - current_accumulator = fields.Str(data_key="currentAccumulator") - timestamp = fields.Int() - - -class RevStatusListState(BaseModel): - """RevStatusListState.""" - - STATE_FINISHED = "finished" - STATE_FAILED = "failed" - STATE_ACTION = "action" - STATE_WAIT = "wait" - - class Meta: - """RevStatusListState metadata.""" - - schema_class = "RevStatusListStateSchema" - - def __init__( - self, - state: str, - revocation_status_list_id: str, - revocation_status_list: RevStatusList, - ): - self.state = state - self.revocation_status_list_id = revocation_status_list_id - self.revocation_status_list = revocation_status_list - - -class RevStatusListStateSchema(BaseModelSchema): - """RevStatusListStateSchema.""" - - class Meta: - """RevStatusListStateSchema metadata.""" - - model_class = RevStatusListState - unknown = EXCLUDE - - state = fields.Str( - validate=OneOf( - [ - RevStatusListState.STATE_FINISHED, - RevStatusListState.STATE_FAILED, - RevStatusListState.STATE_ACTION, - RevStatusListState.STATE_WAIT, - ] - ) - ) - revocation_status_list_id = fields.Str( - description="revocation registry definition id" - ) - revocation_status_list = fields.Nested( - RevStatusListSchema(), description="revocation registry definition" - ) - - -class RevStatusListResult(BaseModel): - """Cred def result.""" - - class Meta: - """RevStatusListResult metadata.""" - - schema_class = "RevStatusListResultSchema" - - def __init__( - self, - job_id: Optional[str], - revocation_status_list_state: RevStatusListState, - registration_metadata: dict, - revocation_status_list_metadata: dict, - **kwargs, - ): - super().__init__(**kwargs) - self.job_id = job_id - self.revocation_status_list_state = revocation_status_list_state - self.registration_metadata = registration_metadata - self.revocation_status_list_metadata = revocation_status_list_metadata - - @property - def rev_reg_def_id(self): - return self.revocation_status_list_state.revocation_status_list_id - - -class RevStatusListResultSchema(BaseModelSchema): - """Cred def result schema.""" - - class Meta: - """RevStatusListResultSchema metadata.""" - - model_class = RevStatusListResult - unknown = EXCLUDE - - job_id = fields.Str() - revocation_status_list_state = fields.Nested(RevStatusListStateSchema()) - registration_metadata = fields.Dict() - # For indy, revocation_status_list_metadata will contain the seqNo - revocation_status_list_metadata = fields.Dict() - - -class AnonCredsRegistryGetRevocationList(BaseModel): - """AnonCredsRegistryGetRevocationList""" - - class Meta: - """AnonCredsRegistryGetRevocationList metadata.""" - - schema_class = "AnonCredsRegistryGetRevocationListSchema" - - def __init__( - self, - revocation_list: RevStatusList, - resolution_metadata: Dict[str, Any], - revocation_registry_metadata: Dict[str, Any], - **kwargs, - ): - super().__init__(**kwargs) - self.revocation_list = revocation_list - self.resolution_metadata = resolution_metadata - self.revocation_registry_metadata = revocation_registry_metadata - - -class AnonCredsRegistryGetRevocationListSchema(BaseModelSchema): - """AnonCredsRegistryGetRevocationListSchema""" - - class Meta: - """AnonCredsRegistryGetRevocationListSchema metadata.""" - - model_class = AnonCredsRegistryGetRevocationList - unknown = EXCLUDE - - revocation_list = fields.Nested(RevStatusListSchema) - resolution_metadata = fields.Str() - revocation_registry_metadata = fields.Dict() diff --git a/aries_cloudagent/anoncreds/models/anoncreds_revocation.py b/aries_cloudagent/anoncreds/models/anoncreds_revocation.py new file mode 100644 index 0000000000..1404974424 --- /dev/null +++ b/aries_cloudagent/anoncreds/models/anoncreds_revocation.py @@ -0,0 +1,427 @@ +"""Anoncreds cred def OpenAPI validators""" +from typing import Any, Dict, List, Optional +from typing_extensions import Literal + +from anoncreds import RevocationRegistryDefinition, RevocationStatusList +from marshmallow import EXCLUDE, fields +from marshmallow.validate import OneOf + +from ...messaging.models.base import BaseModel, BaseModelSchema + + +class RevRegDef(BaseModel): + """RevRegDef""" + + class Meta: + """RevRegDef metadata.""" + + schema_class = "RevRegDefSchema" + + def __init__( + self, + issuer_id: str, + type: Literal["CL_ACCUM"], + cred_def_id: str, + tag: str, + # TODO: determine type for `publicKeys` + public_keys: dict, + max_cred_num: int, + tails_Location: str, + tails_hash: str, + **kwargs, + ): + super().__init__(**kwargs) + self.issuer_id = issuer_id + self.type = type + self.cred_def_id = cred_def_id + self.tag = tag + self.public_keys = public_keys + self.max_cred_num = max_cred_num + self.tails_location = tails_Location + self.tails_hash = tails_hash + + @classmethod + def from_native(cls, rev_reg_def: RevocationRegistryDefinition): + """Convert a native revocation registry definition to a RevRegDef object.""" + return cls.deserialize(rev_reg_def.to_json()) + + def to_native(self): + """Convert to native anoncreds revocation registry definition.""" + return RevocationRegistryDefinition.load(self.serialize()) + + +class RevRegDefSchema(BaseModelSchema): + """RevRegDefSchema.""" + + class Meta: + """RevRegDefSchema metadata.""" + + model_class = RevRegDef + unknown = EXCLUDE + + issuer_id = fields.Str( + description="Issuer Identifier of the credential definition or schema", + data_key="issuerId", + ) + type = fields.Str() + cred_def_id = fields.Str( + description="Credential definition identifier", + data_key="credDefId", + ) + tag = fields.Str(description="""""") + # TODO: type for public key + public_keys = fields.Dict(data_key="publicKeys") + max_cred_num = fields.Int(data_key="maxCredNum") + tails_location = fields.Str(data_key="tailsLocation") + tails_hash = fields.Str(data_key="tailsHash") + + +class RevRegDefState(BaseModel): + """RevRegDefState.""" + + STATE_FINISHED = "finished" + STATE_FAILED = "failed" + STATE_ACTION = "action" + STATE_WAIT = "wait" + + class Meta: + """RevRegDefState metadata.""" + + schema_class = "RevRegDefStateSchema" + + def __init__( + self, + state: str, + revocation_registry_definition_id: str, + revocation_registry_definition: RevRegDef, + ): + self.state = state + self.revocation_registry_definition_id = revocation_registry_definition_id + self.revocation_registry_definition = revocation_registry_definition + + +class RevRegDefStateSchema(BaseModelSchema): + """RevRegDefStateSchema.""" + + class Meta: + """RevRegDefStateSchema metadata.""" + + model_class = RevRegDefState + unknown = EXCLUDE + + state = fields.Str( + validate=OneOf( + [ + RevRegDefState.STATE_FINISHED, + RevRegDefState.STATE_FAILED, + RevRegDefState.STATE_ACTION, + RevRegDefState.STATE_WAIT, + ] + ) + ) + revocation_registry_definition_id = fields.Str( + description="revocation registry definition id" + ) + revocation_registry_definition = fields.Nested( + RevRegDefSchema(), description="revocation registry definition" + ) + + +class RevRegDefResult(BaseModel): + """Cred def result.""" + + class Meta: + """RevRegDefResult metadata.""" + + schema_class = "RevRegDefResultSchema" + + def __init__( + self, + job_id: Optional[str], + revocation_registry_definition_state: RevRegDefState, + registration_metadata: dict, + revocation_registry_definition_metadata: dict, + **kwargs, + ): + super().__init__(**kwargs) + self.job_id = job_id + self.revocation_registry_definition_state = revocation_registry_definition_state + self.registration_metadata = registration_metadata + self.revocation_registry_definition_metadata = ( + revocation_registry_definition_metadata + ) + + @property + def rev_reg_def_id(self): + return ( + self.revocation_registry_definition_state.revocation_registry_definition_id + ) + + +class RevRegDefResultSchema(BaseModelSchema): + """Cred def result schema.""" + + class Meta: + """RevRegDefResultSchema metadata.""" + + model_class = RevRegDefResult + unknown = EXCLUDE + + job_id = fields.Str() + revocation_registry_definition_state = fields.Nested(RevRegDefStateSchema()) + registration_metadata = fields.Dict() + # For indy, revocation_registry_definition_metadata will contain the seqNo + revocation_registry_definition_metadata = fields.Dict() + + +class AnonCredsRegistryGetRevocationRegistryDefinition(BaseModel): + """AnonCredsRegistryGetRevocationRegistryDefinition""" + + class Meta: + """AnonCredsRegistryGetRevocationRegistryDefinition metadata.""" + + schema_class = "AnonCredsRegistryGetRevocationRegistryDefinitionSchema" + + def __init__( + self, + revocation_registry: RevRegDef, + revocation_registry_id: str, + resolution_metadata: Dict[str, Any], + revocation_registry_metadata: Dict[str, Any], + **kwargs, + ): + super().__init__(**kwargs) + self.revocation_registry = revocation_registry + self.revocation_registry_id = revocation_registry_id + self.resolution_metadata = resolution_metadata + self.revocation_registry_metadata = revocation_registry_metadata + + +class AnonCredsRegistryGetRevocationRegistryDefinitionSchema(BaseModelSchema): + class Meta: + """AnonCredsRegistryGetRevocationRegistryDefinitionSchema metadata.""" + + model_class = AnonCredsRegistryGetRevocationRegistryDefinition + unknown = EXCLUDE + + revocation_registry = fields.Nested(RevRegDefSchema()) + revocation_registry_id = fields.Str() + resolution_metadata = fields.Dict() + revocation_registry_metadata = fields.Dict() + + +class AnonCredsRegistryGetRevocationRegistryDefinitions(BaseModel): + """AnonCredsRegistryGetRevocationRegistryDefinitions""" + + class Meta: + """AnonCredsRegistryGetRevocationRegistryDefinitions metadata.""" + + schema_class = "AnonCredsRegistryGetRevocationRegistryDefinitionsSchema" + + def __init__(self, revocation_definition_ids: list, **kwargs): + super().__init__(**kwargs) + self.revocation_definition_ids = revocation_definition_ids + + +class AnonCredsRegistryGetRevocationRegistryDefinitionsSchema(BaseModelSchema): + """AnonCredsRegistryGetRevocationRegistryDefinitionsSchema""" + + class Meta: + """AnonCredsRegistryGetRevocationRegistryDefinitionsSchema metadata""" + + model_class = AnonCredsRegistryGetRevocationRegistryDefinitions + unknown = EXCLUDE + + revocation_definition_ids = fields.List( + fields.Str( + data_key="revocation_definition_ids", + description="credential definition identifiers", + ) + ) + + +class RevStatusList(BaseModel): + """RevStatusList.""" + + class Meta: + """RevStatusList metadata.""" + + schema_class = "AnonCredsRevocationListSchema" + + def __init__( + self, + issuer_id: str, + rev_reg_id: str, + revocation_list: List[int], + current_accumulator: str, + timestamp: int, + **kwargs, + ): + super().__init__(**kwargs) + self.issuer_id = issuer_id + self.rev_reg_id = rev_reg_id + self.revocation_list = revocation_list + self.current_accumulator = current_accumulator + self.timestamp = timestamp + + @classmethod + def from_native(cls, rev_status_list: RevocationStatusList): + """Convert from native revocation status list.""" + return cls.deserialize(rev_status_list.to_json()) + + def to_native(self): + """Convert to native revocation status list.""" + return RevocationStatusList.load(self.serialize()) + + +class RevStatusListSchema(BaseModelSchema): + """RevStatusListSchema.""" + + class Meta: + """RevStatusListSchema metadata.""" + + model_class = RevStatusList + unknown = EXCLUDE + + issuer_id = fields.Str( + description="Issuer Identifier of the credential definition or schema", + data_key="issuerId", + ) + rev_reg_id = fields.Str( + description="", + data_key="revRegId", + ) + revocation_list = fields.List( + fields.Str(description=""), description="", data_key="revocationList" + ) + current_accumulator = fields.Str(data_key="currentAccumulator") + timestamp = fields.Int() + + +class RevStatusListState(BaseModel): + """RevStatusListState.""" + + STATE_FINISHED = "finished" + STATE_FAILED = "failed" + STATE_ACTION = "action" + STATE_WAIT = "wait" + + class Meta: + """RevStatusListState metadata.""" + + schema_class = "RevStatusListStateSchema" + + def __init__( + self, + state: str, + revocation_status_list_id: str, + revocation_status_list: RevStatusList, + ): + self.state = state + self.revocation_status_list_id = revocation_status_list_id + self.revocation_status_list = revocation_status_list + + +class RevStatusListStateSchema(BaseModelSchema): + """RevStatusListStateSchema.""" + + class Meta: + """RevStatusListStateSchema metadata.""" + + model_class = RevStatusListState + unknown = EXCLUDE + + state = fields.Str( + validate=OneOf( + [ + RevStatusListState.STATE_FINISHED, + RevStatusListState.STATE_FAILED, + RevStatusListState.STATE_ACTION, + RevStatusListState.STATE_WAIT, + ] + ) + ) + revocation_status_list_id = fields.Str( + description="revocation registry definition id" + ) + revocation_status_list = fields.Nested( + RevStatusListSchema(), description="revocation registry definition" + ) + + +class RevStatusListResult(BaseModel): + """Cred def result.""" + + class Meta: + """RevStatusListResult metadata.""" + + schema_class = "RevStatusListResultSchema" + + def __init__( + self, + job_id: Optional[str], + revocation_status_list_state: RevStatusListState, + registration_metadata: dict, + revocation_status_list_metadata: dict, + **kwargs, + ): + super().__init__(**kwargs) + self.job_id = job_id + self.revocation_status_list_state = revocation_status_list_state + self.registration_metadata = registration_metadata + self.revocation_status_list_metadata = revocation_status_list_metadata + + @property + def rev_reg_def_id(self): + return self.revocation_status_list_state.revocation_status_list_id + + +class RevStatusListResultSchema(BaseModelSchema): + """Cred def result schema.""" + + class Meta: + """RevStatusListResultSchema metadata.""" + + model_class = RevStatusListResult + unknown = EXCLUDE + + job_id = fields.Str() + revocation_status_list_state = fields.Nested(RevStatusListStateSchema()) + registration_metadata = fields.Dict() + # For indy, revocation_status_list_metadata will contain the seqNo + revocation_status_list_metadata = fields.Dict() + + +class AnonCredsRegistryGetRevocationList(BaseModel): + """AnonCredsRegistryGetRevocationList""" + + class Meta: + """AnonCredsRegistryGetRevocationList metadata.""" + + schema_class = "AnonCredsRegistryGetRevocationListSchema" + + def __init__( + self, + revocation_list: RevStatusList, + resolution_metadata: Dict[str, Any], + revocation_registry_metadata: Dict[str, Any], + **kwargs, + ): + super().__init__(**kwargs) + self.revocation_list = revocation_list + self.resolution_metadata = resolution_metadata + self.revocation_registry_metadata = revocation_registry_metadata + + +class AnonCredsRegistryGetRevocationListSchema(BaseModelSchema): + """AnonCredsRegistryGetRevocationListSchema""" + + class Meta: + """AnonCredsRegistryGetRevocationListSchema metadata.""" + + model_class = AnonCredsRegistryGetRevocationList + unknown = EXCLUDE + + revocation_list = fields.Nested(RevStatusListSchema) + resolution_metadata = fields.Str() + revocation_registry_metadata = fields.Dict() From 9be889e2415b8beb01011ca6281c7436e10f95f0 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Wed, 29 Mar 2023 12:05:45 -0400 Subject: [PATCH 082/150] fix: imports and abstract methods Signed-off-by: Daniel Bluhm --- .../anoncreds/default/did_indy_registry/registry.py | 4 ++-- .../anoncreds/default/did_web_registry/registry.py | 4 ++-- .../default/legacy_indy_registry/registry.py | 4 ++-- aries_cloudagent/anoncreds/issuer.py | 11 +++++++---- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py index f79a27328a..aafceb4d5b 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py @@ -8,7 +8,7 @@ from ....models.anoncreds_cred_def import ( GetCredDefResult, ) -from ..models.anoncreds_revocation import ( +from ....models.anoncreds_revocation import ( AnonCredsRegistryGetRevocationList, AnonCredsRegistryGetRevocationRegistryDefinition, ) @@ -91,5 +91,5 @@ async def get_revocation_list( """Get a revocation list from the registry.""" # TODO: determine keyword arguments - async def register_revocation_list(self): + async def register_revocation_status_list(self): """Register a revocation list on the registry.""" diff --git a/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py index f3fa4773e3..d82b86dc70 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py @@ -7,7 +7,7 @@ from ....models.anoncreds_cred_def import ( GetCredDefResult, ) -from ..models.anoncreds_revocation import ( +from ....models.anoncreds_revocation import ( AnonCredsRegistryGetRevocationList, AnonCredsRegistryGetRevocationRegistryDefinition, ) @@ -83,5 +83,5 @@ async def get_revocation_list( """Get a revocation list from the registry.""" # TODO: determine keyword arguments - async def register_revocation_list(self): + async def register_revocation_status_list(self): """Register a revocation list on the registry.""" diff --git a/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py index 68e7447332..be1268b8fe 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py @@ -25,7 +25,7 @@ CredDefResult, GetCredDefResult, ) -from ..models.anoncreds_revocation import ( +from ....models.anoncreds_revocation import ( AnonCredsRegistryGetRevocationList, AnonCredsRegistryGetRevocationRegistryDefinition, ) @@ -364,5 +364,5 @@ async def get_revocation_list( """Get a revocation list from the registry.""" # TODO: determine keyword arguments - async def register_revocation_list(self): + async def register_revocation_status_list(self): """Register a revocation list on the registry.""" diff --git a/aries_cloudagent/anoncreds/issuer.py b/aries_cloudagent/anoncreds/issuer.py index 2de2d3ac6b..bba4bf57fc 100644 --- a/aries_cloudagent/anoncreds/issuer.py +++ b/aries_cloudagent/anoncreds/issuer.py @@ -3,6 +3,8 @@ from abc import ABC, ABCMeta, abstractmethod from typing import Optional, Sequence, Tuple +from aries_cloudagent.anoncreds.models.anoncreds_revocation import RevRegDefResult + from ..core.error import BaseError from .models.anoncreds_cred_def import CredDefResult from .models.anoncreds_schema import SchemaResult @@ -189,15 +191,16 @@ async def revoke_credentials( """ @abstractmethod - async def create_and_store_revocation_registry( + async def create_and_register_revocation_registry_definition( self, - origin_did: str, + issuer_id: str, cred_def_id: str, - revoc_def_type: str, + registry_type: str, tag: str, max_cred_num: int, tails_base_path: str, - ) -> Tuple[str, str, str]: + options: Optional[dict] = None, + ) -> RevRegDefResult: """ Create a new revocation registry and store it in the wallet. From ae2f64d6f5094df0369671829798cdb82b66e868 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Wed, 29 Mar 2023 13:54:46 -0400 Subject: [PATCH 083/150] fix: issues with cred issuance and verification flows Signed-off-by: Daniel Bluhm --- .../anoncreds/anoncreds/issuer.py | 84 ++++++++++++++++--- .../anoncreds/anoncreds/routes.py | 2 +- aries_cloudagent/anoncreds/issuer.py | 20 ++++- .../v2_0/formats/indy/handler.py | 24 ++---- 4 files changed, 99 insertions(+), 31 deletions(-) diff --git a/aries_cloudagent/anoncreds/anoncreds/issuer.py b/aries_cloudagent/anoncreds/anoncreds/issuer.py index bdddda29f2..9f582600ac 100644 --- a/aries_cloudagent/anoncreds/anoncreds/issuer.py +++ b/aries_cloudagent/anoncreds/anoncreds/issuer.py @@ -2,7 +2,7 @@ import asyncio import logging -import time +from time import time from typing import Optional, Sequence, Tuple @@ -250,7 +250,7 @@ async def credential_definition_in_wallet( "Error checking for credential definition" ) from err - async def create_and_store_credential_definition( + async def create_and_register_credential_definition( self, issuer_id: str, schema_id: str, @@ -318,10 +318,12 @@ async def create_and_store_credential_definition( # Note: Indy-SDK uses a separate SchemaId record for this tags={ "schema_id": schema_id, + "schema_issuer_id": schema_result.schema.issuer_id, "issuer_id": issuer_id, "schema_name": schema_result.schema.name, "schema_version": schema_result.schema.version, "state": result.credential_definition_state.state, + "epoch": str(int(time())), }, ) await txn.handle.insert( @@ -349,30 +351,86 @@ async def finish_cred_def(self, cred_def_id: str): async def get_created_credential_definitions( self, issuer_id: Optional[str] = None, + schema_issuer_id: Optional[str] = None, schema_id: Optional[str] = None, schema_name: Optional[str] = None, schema_version: Optional[str] = None, state: Optional[str] = None, + epoch: Optional[str] = None, ) -> Sequence[str]: """Retrieve IDs of credential definitions previously created.""" async with self._profile.session() as session: # TODO limit? scan? - credential_definitions = await session.handle.fetch_all( + credential_definition_entries = await session.handle.fetch_all( CATEGORY_CRED_DEF, { key: value for key, value in { "issuer_id": issuer_id, + "schema_issuer_id": schema_issuer_id, "schema_id": schema_id, "schema_name": schema_name, "schema_version": schema_version, "state": state, + "epoch": epoch, }.items() if value is not None }, ) - # entry.name was stored as the credential_definition's ID - return [entry.name for entry in credential_definitions] + return [entry.name for entry in credential_definition_entries] + + async def match_created_credential_definitions( + self, + cred_def_id: Optional[str] = None, + issuer_id: Optional[str] = None, + schema_issuer_id: Optional[str] = None, + schema_id: Optional[str] = None, + schema_name: Optional[str] = None, + schema_version: Optional[str] = None, + state: Optional[str] = None, + epoch: Optional[str] = None, + ) -> Optional[str]: + """Return cred def id of most recent matching cred def.""" + async with self._profile.session() as session: + # TODO limit? scan? + if cred_def_id: + cred_def_entry = await session.handle.fetch( + CATEGORY_CRED_DEF, cred_def_id + ) + else: + credential_definition_entries = await session.handle.fetch_all( + CATEGORY_CRED_DEF, + { + key: value + for key, value in { + "issuer_id": issuer_id, + "schema_issuer_id": schema_issuer_id, + "schema_id": schema_id, + "schema_name": schema_name, + "schema_version": schema_version, + "state": state, + "epoch": epoch, + }.items() + if value is not None + }, + ) + cred_def_entry = max( + [entry for entry in credential_definition_entries], + key=lambda r: int(r.tags["epoch"]), + ) + + if cred_def_entry: + return cred_def_entry.name + + return None + + async def cred_def_supports_revocation(self, cred_def_id: str) -> bool: + """Return whether a credential definition supports revocation.""" + anoncreds_registry = self.profile.inject(AnonCredsRegistry) + cred_def_result = await anoncreds_registry.get_credential_definition( + self.profile, cred_def_id + ) + return cred_def_result.credential_definition.value.revocation is not None async def create_and_register_revocation_registry_definition( self, @@ -587,18 +645,18 @@ async def create_credential_offer(self, credential_definition_id: str) -> str: async def create_credential( self, - schema: dict, + schema_id: str, credential_offer: dict, credential_request: dict, credential_values: dict, - revoc_reg_id: str = None, - tails_file_path: str = None, + revoc_reg_id: Optional[str] = None, + tails_file_path: Optional[str] = None, ) -> Tuple[str, str]: """ Create a credential. Args - schema: Schema to create credential for + schema_id: Schema ID to create credential for credential_offer: Credential Offer to create credential for credential_request: Credential request to create credential for credential_values: Values to go in credential @@ -609,6 +667,8 @@ async def create_credential( A tuple of created credential and revocation id """ + anoncreds_registry = self.profile.inject(AnonCredsRegistry) + schema_result = await anoncreds_registry.get_schema(self.profile, schema_id) credential_definition_id = credential_offer["cred_def_id"] try: async with self._profile.session() as session: @@ -628,7 +688,7 @@ async def create_credential( ) raw_values = {} - schema_attributes = schema["attrNames"] + schema_attributes = schema_result.schema.attr_names for attribute in schema_attributes: # Ensure every attribute present in schema to be set. # Extraneous attribute names are ignored. @@ -682,6 +742,9 @@ async def create_credential( rev_reg_def = RevocationRegistryDefinition.load( rev_reg_def.raw_value ) + rev_status_list = RevocationStatusList.load( + rev_status_list.raw_value + ) except AnoncredsError as err: raise AnonCredsIssuerError( "Error loading revocation registry definition" @@ -710,6 +773,7 @@ async def create_credential( else: revoc = None credential_revocation_id = None + rev_status_list = None try: credential = await asyncio.get_event_loop().run_in_executor( diff --git a/aries_cloudagent/anoncreds/anoncreds/routes.py b/aries_cloudagent/anoncreds/anoncreds/routes.py index 876deb27fa..9eb94bd5d8 100644 --- a/aries_cloudagent/anoncreds/anoncreds/routes.py +++ b/aries_cloudagent/anoncreds/anoncreds/routes.py @@ -270,7 +270,7 @@ async def cred_def_post(request: web.BaseRequest): tag = cred_def.get("tag") issuer = context.inject(AnonCredsIssuer) - result = await issuer.create_and_store_credential_definition( + result = await issuer.create_and_register_credential_definition( issuer_id, schema_id, tag, diff --git a/aries_cloudagent/anoncreds/issuer.py b/aries_cloudagent/anoncreds/issuer.py index bba4bf57fc..1015793712 100644 --- a/aries_cloudagent/anoncreds/issuer.py +++ b/aries_cloudagent/anoncreds/issuer.py @@ -97,7 +97,7 @@ async def credential_definition_in_wallet( """ @abstractmethod - async def create_and_store_credential_definition( + async def create_and_register_credential_definition( self, issuer_id: str, schema_id: str, @@ -131,6 +131,24 @@ async def get_created_credential_definitions( ) -> Sequence[str]: """Get created credential definitions.""" + @abstractmethod + async def match_created_credential_definitions( + self, + cred_def_id: Optional[str] = None, + issuer_id: Optional[str] = None, + schema_issuer_id: Optional[str] = None, + schema_id: Optional[str] = None, + schema_name: Optional[str] = None, + schema_version: Optional[str] = None, + state: Optional[str] = None, + epoch: Optional[str] = None, + ) -> Optional[str]: + """Return cred def id of most recent matching cred def.""" + + @abstractmethod + async def cred_def_supports_revocation(self, cred_def_id: str) -> bool: + """Return whether a credential definition supports revocation.""" + @abstractmethod async def create_credential_offer(self, credential_definition_id) -> str: """ diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/handler.py b/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/handler.py index 31102eb443..27f1b59309 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/handler.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/handler.py @@ -19,7 +19,6 @@ from ......ledger.base import BaseLedger from ......ledger.multiple_ledger.ledger_requests_executor import ( GET_CRED_DEF, - GET_SCHEMA, IndyLedgerRequestsExecutor, ) from ......messaging.credential_definitions.util import ( @@ -195,8 +194,8 @@ async def create_offer( ledger = self.profile.inject(BaseLedger) cache = self.profile.inject_or(BaseCache) - cred_def_id = await self._match_sent_cred_def_id( - cred_proposal_message.attachment(IndyCredFormatHandler.format) + cred_def_id = await issuer.match_created_credential_definitions( + **cred_proposal_message.attachment(IndyCredFormatHandler.format) ) async def _create(): @@ -341,21 +340,7 @@ async def issue_credential( cred_def_id = cred_offer["cred_def_id"] issuer = self.profile.inject(AnonCredsIssuer) - 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( - schema_id, - txn_record_type=GET_SCHEMA, - ) - )[1] - async with ledger: - schema = await ledger.get_schema(schema_id) - cred_def = await ledger.get_credential_definition(cred_def_id) - revocable = cred_def["value"].get("revocation") + revocable = await issuer.cred_def_supports_revocation(cred_def_id) result = None for attempt in range(max(retries, 1)): @@ -367,6 +352,7 @@ async def issue_credential( await asyncio.sleep(2) if revocable: + # TODO make this go through the anoncreds interface revoc = IndyRevocation(self.profile) registry_info = await revoc.get_or_create_active_registry(cred_def_id) if not registry_info: @@ -381,7 +367,7 @@ async def issue_credential( try: (cred_json, cred_rev_id) = await issuer.create_credential( - schema, + schema_id, cred_offer, cred_request, cred_values, From f2738ee1d492bc8a6caf0a4a56c432d36544fdd7 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Wed, 29 Mar 2023 13:56:06 -0400 Subject: [PATCH 084/150] test: add simple test scripts for anoncreds Signed-off-by: Daniel Bluhm --- anoncreds_test.py | 86 ++++++++++++++++++++++++++++++++ docker-compose.yml | 67 +++++++++++++++++++++++++ docker/Dockerfile.anoncreds-test | 8 +++ docker/Dockerfile.run | 1 + healthcheck.py | 25 ++++++++++ 5 files changed, 187 insertions(+) create mode 100644 anoncreds_test.py create mode 100644 docker-compose.yml create mode 100644 docker/Dockerfile.anoncreds-test create mode 100644 healthcheck.py diff --git a/anoncreds_test.py b/anoncreds_test.py new file mode 100644 index 0000000000..4bfe8ec99a --- /dev/null +++ b/anoncreds_test.py @@ -0,0 +1,86 @@ +import os + +from controller.controller import Controller +from controller.protocols import ( + indy_anoncred_onboard, + didexchange, + indy_issue_credential_v2, + indy_present_proof_v2, +) +from controller.logging import logging_to_stdout + +ALICE = os.getenv("ALICE", "http://alice:3001") +BOB = os.getenv("BOB", "http://bob:3005") + + +async def main(): + logging_to_stdout() + async with Controller(base_url=ALICE) as alice, Controller(base_url=BOB) as bob: + # DID Setup + public_did = await indy_anoncred_onboard(alice) + + # Register a Schema using legacy Indy + response = await alice.post( + "/anoncreds/schema", + json={ + "schema": { + "attrNames": ["name", "age"], + "issuerId": public_did.did, + "name": "anoncreds-testing", + "version": "0.1", + }, + "options": {}, + }, + ) + schema_id = response["schema_state"]["schema_id"] + schema = await alice.get(f"/anoncreds/schema/{schema_id}") + schemas = await alice.get("/anoncreds/schemas") + + cred_def = await alice.post( + "/anoncreds/credential-definition", + json={ + "credential_definition": { + "tag": "default", + "schemaId": schema_id, + "issuerId": public_did.did, + }, + "options": { + "support_revocation": False, + }, + }, + ) + cred_def_id = cred_def["credential_definition_state"][ + "credential_definition_id" + ] + cred_def = await alice.get(f"/anoncreds/credential-definition/{cred_def_id}") + cred_defs = await alice.get("/anoncreds/credential-definitions") + + alice_conn, bob_conn = await didexchange(alice, bob) + await indy_issue_credential_v2( + alice, + bob, + alice_conn.connection_id, + bob_conn.connection_id, + cred_def_id, + {"name": "Bob", "age": "42"}, + ) + bob_pres, alice_pres = await indy_present_proof_v2( + bob, + alice, + bob_conn.connection_id, + alice_conn.connection_id, + name="proof-1", + version="0.1", + comment="testing", + requested_attributes=[ + {"name": "name", "restrictions": [{"cred_def_id": cred_def_id}]}, + {"name": "age", "restrictions": [{"cred_def_id": cred_def_id}]}, + ], + ) + print(alice_pres.verified) + + +if __name__ == "__main__": + import asyncio + + asyncio.run(main()) diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000000..0bf845c634 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,67 @@ +version: '3' +services: + alice: + image: acapy-run + build: + context: . + dockerfile: ./docker/Dockerfile.run + ports: + - 3001:3001 + volumes: + - ./aries_cloudagent:/usr/src/app/aries_cloudagent:z + command: > + start + -it http 0.0.0.0 3000 -ot http -e http://alice:3000 + --admin 0.0.0.0 3001 --admin-insecure-mode + --wallet-type askar + --wallet-name default + --wallet-key test + --auto-provision + --genesis-url https://raw.githubusercontent.com/Indicio-tech/indicio-network/main/genesis_files/pool_transactions_testnet_genesis + --log-level debug + healthcheck: + test: ["CMD-SHELL", "python", "healthcheck.py", "http://localhost:3001/status/live"] + start_period: 10s + interval: 3s + timeout: 5s + retries: 5 + + bob: + image: acapy-run + ports: + - 3005:3005 + volumes: + - ./aries_cloudagent:/usr/src/app/aries_cloudagent:z + command: > + start + -it http 0.0.0.0 3004 -ot http -e http://bob:3004 + --admin 0.0.0.0 3005 --admin-insecure-mode + --wallet-type askar + --wallet-name default + --wallet-key test + --auto-provision + --genesis-url https://raw.githubusercontent.com/Indicio-tech/indicio-network/main/genesis_files/pool_transactions_testnet_genesis + --log-level debug + healthcheck: + test: ["CMD-SHELL", "python", "healthcheck.py", "http://localhost:3005/status/live"] + start_period: 10s + interval: 3s + timeout: 5s + retries: 5 + + + tests: + image: anoncreds-test + build: + context: . + dockerfile: ./docker/Dockerfile.anoncreds-test + environment: + ALICE: "http://alice:3001" + BOB: "http://bob:3005" + volumes: + - ./anoncreds_test.py:/usr/src/app/anoncreds_test.py:z + depends_on: + alice: + condition: service_healthy + bob: + condition: service_healthy diff --git a/docker/Dockerfile.anoncreds-test b/docker/Dockerfile.anoncreds-test new file mode 100644 index 0000000000..647bf12a0f --- /dev/null +++ b/docker/Dockerfile.anoncreds-test @@ -0,0 +1,8 @@ +FROM python:3.9-slim +WORKDIR /usr/src/app + +RUN apt-get update && apt-get install -y git +RUN pip install git+https://github.com/Indicio-tech/acapy-minimal-example.git + +ADD anoncreds_test.py . +CMD ["python", "anoncreds_test.py"] diff --git a/docker/Dockerfile.run b/docker/Dockerfile.run index 8f55992959..614cb805e7 100644 --- a/docker/Dockerfile.run +++ b/docker/Dockerfile.run @@ -24,6 +24,7 @@ ADD aries_cloudagent/version.py aries_cloudagent/version.py ADD bin ./bin ADD README.md ./ ADD setup.py ./ +ADD healthcheck.py ./ RUN pip3 install --no-cache-dir -e . diff --git a/healthcheck.py b/healthcheck.py new file mode 100644 index 0000000000..eba6b1ef54 --- /dev/null +++ b/healthcheck.py @@ -0,0 +1,25 @@ +import sys +from urllib.request import urlopen +from urllib.error import URLError, HTTPError + + +def check_url(url): + try: + with urlopen(url) as response: + return response.status == 200 + except HTTPError as e: + print(f"Error: {e}") + return False + except URLError as e: + print(f"Error: {e}") + return False + + +if __name__ == "__main__": + if len(sys.argv) < 2: + print("Usage: python healthcheck.py ") + sys.exit(1) + + url = sys.argv[1] + is_healthy = check_url(url) + sys.exit(0 if is_healthy else 1) From f5786604747bfcd9d23d4cb8fff1461f3068d71e Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Wed, 29 Mar 2023 17:56:02 -0400 Subject: [PATCH 085/150] feat: verification through anoncreds interface Signed-off-by: Daniel Bluhm --- .../anoncreds/anoncreds/anoncreds_registry.py | 18 ++-- .../anoncreds/anoncreds/base_registry.py | 6 +- .../default/did_indy_registry/registry.py | 8 +- .../default/did_web_registry/registry.py | 8 +- .../default/legacy_indy_registry/registry.py | 8 +- .../anoncreds/anoncreds/verifier.py | 83 +++++++++++++++++-- .../anoncreds/models/anoncreds_revocation.py | 16 ++-- aries_cloudagent/anoncreds/verifier.py | 58 ++++++------- aries_cloudagent/askar/profile.py | 2 +- .../present_proof/indy/pres_exch_handler.py | 80 +----------------- .../protocols/present_proof/v1_0/manager.py | 8 +- .../v2_0/formats/indy/handler.py | 8 +- 12 files changed, 147 insertions(+), 156 deletions(-) diff --git a/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py b/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py index f0d9f5f0a3..eb3923e469 100644 --- a/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py @@ -10,7 +10,7 @@ GetCredDefResult, ) from ..models.anoncreds_revocation import ( - AnonCredsRegistryGetRevocationList, + GetRevStatusListResult, AnonCredsRegistryGetRevocationRegistryDefinition, RevRegDef, RevRegDefResult, @@ -118,11 +118,13 @@ async def register_credential_definition( ) async def get_revocation_registry_definition( - self, revocation_registry_id: str + self, profile: Profile, revocation_registry_id: str ) -> AnonCredsRegistryGetRevocationRegistryDefinition: """Get a revocation registry definition from the registry.""" resolver = await self._resolver_for_identifier(revocation_registry_id) - return await resolver.get_revocation_registry_definition(revocation_registry_id) + return await resolver.get_revocation_registry_definition( + profile, revocation_registry_id + ) async def register_revocation_registry_definition( self, @@ -138,12 +140,14 @@ async def register_revocation_registry_definition( profile, revocation_registry_definition, options ) - async def get_revocation_list( - self, revocation_registry_id: str, timestamp: str - ) -> AnonCredsRegistryGetRevocationList: + async def get_revocation_status_list( + self, profile: Profile, revocation_registry_id: str, timestamp: str + ) -> GetRevStatusListResult: """Get a revocation list from the registry.""" resolver = await self._resolver_for_identifier(revocation_registry_id) - return await resolver.get_revocation_list(revocation_registry_id, timestamp) + return await resolver.get_revocation_status_list( + profile, revocation_registry_id, timestamp + ) async def register_revocation_status_list( self, diff --git a/aries_cloudagent/anoncreds/anoncreds/base_registry.py b/aries_cloudagent/anoncreds/anoncreds/base_registry.py index cf3a0248eb..1baba21a8f 100644 --- a/aries_cloudagent/anoncreds/anoncreds/base_registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/base_registry.py @@ -11,7 +11,7 @@ GetCredDefResult, ) from ..models.anoncreds_revocation import ( - AnonCredsRegistryGetRevocationList, + GetRevStatusListResult, AnonCredsRegistryGetRevocationRegistryDefinition, RevRegDef, RevRegDefResult, @@ -110,9 +110,9 @@ async def get_revocation_registry_definition( """Get a revocation registry definition from the registry.""" @abstractmethod - async def get_revocation_list( + async def get_revocation_status_list( self, profile: Profile, revocation_registry_id: str, timestamp: str - ) -> AnonCredsRegistryGetRevocationList: + ) -> GetRevStatusListResult: """Get a revocation list from the registry.""" diff --git a/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py index aafceb4d5b..7fad17b0e5 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py @@ -9,7 +9,7 @@ GetCredDefResult, ) from ....models.anoncreds_revocation import ( - AnonCredsRegistryGetRevocationList, + GetRevStatusListResult, AnonCredsRegistryGetRevocationRegistryDefinition, ) from ....models.anoncreds_schema import GetSchemaResult @@ -85,9 +85,9 @@ async def register_revocation_registry_definition( async def get_revocation_registry_definitions(self, profile: Profile, filter: str): """Get credential definition ids filtered by filter""" - async def get_revocation_list( - self, revocation_registry_id: str, timestamp: str - ) -> AnonCredsRegistryGetRevocationList: + async def get_revocation_status_list( + self, profile: Profile, revocation_registry_id: str, timestamp: str + ) -> GetRevStatusListResult: """Get a revocation list from the registry.""" # TODO: determine keyword arguments diff --git a/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py index d82b86dc70..ef969d0f4a 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py @@ -8,7 +8,7 @@ GetCredDefResult, ) from ....models.anoncreds_revocation import ( - AnonCredsRegistryGetRevocationList, + GetRevStatusListResult, AnonCredsRegistryGetRevocationRegistryDefinition, ) from ....models.anoncreds_schema import GetSchemaResult @@ -77,9 +77,9 @@ async def register_revocation_registry_definition(self): async def get_revocation_registry_definitions(self, profile: Profile, filter: str): """Get credential definition ids filtered by filter""" - async def get_revocation_list( - self, revocation_registry_id: str, timestamp: str - ) -> AnonCredsRegistryGetRevocationList: + async def get_revocation_status_list( + self, profile: Profile, revocation_registry_id: str, timestamp: str + ) -> GetRevStatusListResult: """Get a revocation list from the registry.""" # TODO: determine keyword arguments diff --git a/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py index be1268b8fe..9433d26379 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py @@ -26,7 +26,7 @@ GetCredDefResult, ) from ....models.anoncreds_revocation import ( - AnonCredsRegistryGetRevocationList, + GetRevStatusListResult, AnonCredsRegistryGetRevocationRegistryDefinition, ) from ....models.anoncreds_schema import ( @@ -358,9 +358,9 @@ async def register_revocation_registry_definition( except RevocationError as err: raise AnonCredsRegistrationError(err) - async def get_revocation_list( - self, revocation_registry_id: str, timestamp: str - ) -> AnonCredsRegistryGetRevocationList: + async def get_revocation_status_list( + self, profile: Profile, revocation_registry_id: str, timestamp: str + ) -> GetRevStatusListResult: """Get a revocation list from the registry.""" # TODO: determine keyword arguments diff --git a/aries_cloudagent/anoncreds/anoncreds/verifier.py b/aries_cloudagent/anoncreds/anoncreds/verifier.py index e0fc102e8b..ba45c1c5eb 100644 --- a/aries_cloudagent/anoncreds/anoncreds/verifier.py +++ b/aries_cloudagent/anoncreds/anoncreds/verifier.py @@ -2,9 +2,12 @@ import asyncio import logging +from typing import Tuple from anoncreds import AnoncredsError, Presentation +from aries_cloudagent.anoncreds.anoncreds.anoncreds_registry import AnonCredsRegistry + from ...core.profile import Profile from ..verifier import AnonCredsVerifier, PresVerifyMsg @@ -12,12 +15,12 @@ LOGGER = logging.getLogger(__name__) -class IndyCredxVerifier(AnonCredsVerifier): - """Indy-Credx verifier class.""" +class AnonCredsRsVerifier(AnonCredsVerifier): + """Verifier class.""" def __init__(self, profile: Profile): """ - Initialize an IndyCredxVerifier instance. + Initialize an AnonCredsRsVerifier instance. Args: profile: an active profile instance @@ -25,6 +28,68 @@ def __init__(self, profile: Profile): """ self.profile = profile + async def process_pres_identifiers( + self, + identifiers: list, + ) -> Tuple[dict, dict, dict, dict]: + """Return schemas, cred_defs, rev_reg_defs, rev_status_lists.""" + schema_ids = [] + cred_def_ids = [] + + schemas = {} + cred_defs = {} + rev_reg_defs = {} + rev_status_lists = {} + + for identifier in identifiers: + schema_ids.append(identifier["schema_id"]) + cred_def_ids.append(identifier["cred_def_id"]) + + anoncreds_registry = self.profile.inject(AnonCredsRegistry) + # Build schemas for anoncreds + if identifier["schema_id"] not in schemas: + schemas[identifier["schema_id"]] = ( + await anoncreds_registry.get_schema( + self.profile, identifier["schema_id"] + ) + ).schema.serialize() + if identifier["cred_def_id"] not in cred_defs: + cred_defs[identifier["cred_def_id"]] = ( + await anoncreds_registry.get_credential_definition( + self.profile, identifier["cred_def_id"] + ) + ).credential_definition.serialize() + + if identifier.get("rev_reg_id"): + if identifier["rev_reg_id"] not in rev_reg_defs: + rev_reg_defs[identifier["rev_reg_id"]] = ( + await anoncreds_registry.get_revocation_registry_definition( + self.profile, identifier["rev_reg_id"] + ) + ).revocation_registry.serialize() + + if identifier.get("timestamp"): + rev_status_lists.setdefault(identifier["rev_reg_id"], {}) + + if ( + identifier["timestamp"] + not in rev_status_lists[identifier["rev_reg_id"]] + ): + result = await anoncreds_registry.get_revocation_status_list( + self.profile, + identifier["rev_reg_id"], + identifier["timestamp"], + ) + rev_status_lists[identifier["rev_reg_id"]][ + identifier["timestamp"] + ] = result.revocation_list.serialize() + return ( + schemas, + cred_defs, + rev_reg_defs, + rev_status_lists, + ) + async def verify_presentation( self, pres_req, @@ -32,8 +97,8 @@ async def verify_presentation( schemas, credential_definitions, rev_reg_defs, - rev_reg_entries, - ) -> (bool, list): + rev_status_lists, + ) -> Tuple[bool, list]: """ Verify a presentation. @@ -68,10 +133,10 @@ async def verify_presentation( None, presentation.verify, pres_req, - schemas.values(), - credential_definitions.values(), - rev_reg_defs.values(), - rev_reg_entries, + schemas, + credential_definitions, + rev_reg_defs, + rev_status_lists, ) except AnoncredsError as err: s = str(err) diff --git a/aries_cloudagent/anoncreds/models/anoncreds_revocation.py b/aries_cloudagent/anoncreds/models/anoncreds_revocation.py index 1404974424..c15883ca40 100644 --- a/aries_cloudagent/anoncreds/models/anoncreds_revocation.py +++ b/aries_cloudagent/anoncreds/models/anoncreds_revocation.py @@ -392,13 +392,13 @@ class Meta: revocation_status_list_metadata = fields.Dict() -class AnonCredsRegistryGetRevocationList(BaseModel): - """AnonCredsRegistryGetRevocationList""" +class GetRevStatusListResult(BaseModel): + """GetRevStatusListResult""" class Meta: - """AnonCredsRegistryGetRevocationList metadata.""" + """GetRevStatusListResult metadata.""" - schema_class = "AnonCredsRegistryGetRevocationListSchema" + schema_class = "GetRevStatusListResultSchema" def __init__( self, @@ -413,13 +413,13 @@ def __init__( self.revocation_registry_metadata = revocation_registry_metadata -class AnonCredsRegistryGetRevocationListSchema(BaseModelSchema): - """AnonCredsRegistryGetRevocationListSchema""" +class GetRevStatusListResultSchema(BaseModelSchema): + """GetRevStatusListResultSchema""" class Meta: - """AnonCredsRegistryGetRevocationListSchema metadata.""" + """GetRevStatusListResultSchema metadata.""" - model_class = AnonCredsRegistryGetRevocationList + model_class = GetRevStatusListResult unknown = EXCLUDE revocation_list = fields.Nested(RevStatusListSchema) diff --git a/aries_cloudagent/anoncreds/verifier.py b/aries_cloudagent/anoncreds/verifier.py index 5d4203e46b..9ca7df31b6 100644 --- a/aries_cloudagent/anoncreds/verifier.py +++ b/aries_cloudagent/anoncreds/verifier.py @@ -1,20 +1,15 @@ """Base Indy Verifier class.""" -import logging - from abc import ABC, ABCMeta, abstractmethod from enum import Enum +import logging from time import time -from typing import Mapping +from typing import List, Mapping, Tuple from ..core.profile import Profile -from ..ledger.multiple_ledger.ledger_requests_executor import ( - GET_CRED_DEF, - IndyLedgerRequestsExecutor, -) from ..messaging.util import canon, encode -from ..multitenant.base import BaseMultitenantManager - +from .anoncreds.anoncreds_registry import AnonCredsRegistry +from .models.anoncreds_cred_def import GetCredDefResult from .models.xform import indy_proof_req2non_revoc_intervals @@ -121,7 +116,7 @@ async def check_timestamps( superfluous or missing. Args: - ledger: the base ledger for retrieving revocation registry definitions + profile: relevant profile pres_req: indy proof request pres: indy proof request rev_reg_defs: rev reg defs by rev reg id, augmented with transaction times @@ -130,27 +125,19 @@ async def check_timestamps( now = int(time()) non_revoc_intervals = indy_proof_req2non_revoc_intervals(pres_req) LOGGER.debug(f">>> got non-revoc intervals: {non_revoc_intervals}") + # timestamp for irrevocable credential - cred_defs = [] + cred_defs: List[GetCredDefResult] = [] for index, ident in enumerate(pres["identifiers"]): LOGGER.debug(f">>> got (index, ident): ({index},{ident})") cred_def_id = ident["cred_def_id"] - multitenant_mgr = profile.inject_or(BaseMultitenantManager) - if multitenant_mgr: - ledger_exec_inst = IndyLedgerRequestsExecutor(profile) - else: - ledger_exec_inst = 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) - cred_defs.append(cred_def) + anoncreds_registry = profile.inject(AnonCredsRegistry) + cred_def_result = await anoncreds_registry.get_credential_definition( + profile, cred_def_id + ) + cred_defs.append(cred_def_result) if ident.get("timestamp"): - if not cred_def["value"].get("revocation"): + if not cred_def_result.credential_definition.value.revocation: raise ValueError( f"Timestamp in presentation identifier #{index} " f"for irrevocable cred def id {cred_def_id}" @@ -188,7 +175,7 @@ async def check_timestamps( if "name" in req_attr: if uuid in revealed_attrs: index = revealed_attrs[uuid]["sub_proof_index"] - if cred_defs[index]["value"].get("revocation"): + if cred_defs[index].credential_definition.value.revocation: timestamp = pres["identifiers"][index].get("timestamp") if (timestamp is not None) ^ bool( non_revoc_intervals.get(uuid) @@ -235,7 +222,7 @@ async def check_timestamps( ): raise ValueError(f"Missing requested attribute group {uuid}") index = group_spec["sub_proof_index"] - if cred_defs[index]["value"].get("revocation"): + if cred_defs[index].credential_definition.value.revocation: timestamp = pres["identifiers"][index].get("timestamp") if (timestamp is not None) ^ bool(non_revoc_intervals.get(uuid)): raise ValueError( @@ -265,7 +252,7 @@ async def check_timestamps( f"Presentation predicates mismatch requested predicate {uuid}" ) index = pred_spec["sub_proof_index"] - if cred_defs[index]["value"].get("revocation"): + if cred_defs[index].credential_definition.value.revocation: timestamp = pres["identifiers"][index].get("timestamp") if (timestamp is not None) ^ bool(non_revoc_intervals.get(uuid)): raise ValueError( @@ -389,15 +376,22 @@ async def pre_verify(self, pres_req: dict, pres: dict) -> list: return msgs @abstractmethod - def verify_presentation( + async def process_pres_identifiers( + self, + identifiers: list, + ) -> Tuple[dict, dict, dict, dict]: + """Return schemas, cred_defs, rev_reg_defs, rev_status_lists.""" + + @abstractmethod + async def verify_presentation( self, presentation_request, presentation, schemas, credential_definitions, rev_reg_defs, - rev_reg_entries, - ) -> (bool, list): + rev_status_lists, + ) -> Tuple[bool, list]: """ Verify a presentation. diff --git a/aries_cloudagent/askar/profile.py b/aries_cloudagent/askar/profile.py index cbd63a3aa0..fc89377aba 100644 --- a/aries_cloudagent/askar/profile.py +++ b/aries_cloudagent/askar/profile.py @@ -130,7 +130,7 @@ def bind_providers(self): injector.bind_provider( AnonCredsVerifier, ClassProvider( - "aries_cloudagent.anoncreds.credx.verifier.IndyCredxVerifier", + "aries_cloudagent.anoncreds.anoncreds.verifier.AnonCredsRsVerifier", ref(self), ), ) diff --git a/aries_cloudagent/protocols/present_proof/indy/pres_exch_handler.py b/aries_cloudagent/protocols/present_proof/indy/pres_exch_handler.py index fd627cf83e..07ebb01bd2 100644 --- a/aries_cloudagent/protocols/present_proof/indy/pres_exch_handler.py +++ b/aries_cloudagent/protocols/present_proof/indy/pres_exch_handler.py @@ -2,21 +2,19 @@ import json import logging import time +from typing import Union -from typing import Union, Tuple - -from ....core.error import BaseError -from ....core.profile import Profile from ....anoncreds.holder import AnonCredsHolder, AnonCredsHolderError from ....anoncreds.models.xform import indy_proof_req2non_revoc_intervals +from ....core.error import BaseError +from ....core.profile import Profile from ....ledger.multiple_ledger.ledger_requests_executor import ( - GET_SCHEMA, GET_REVOC_REG_DELTA, + GET_SCHEMA, IndyLedgerRequestsExecutor, ) from ....multitenant.base import BaseMultitenantManager from ....revocation.models.revocation_registry import RevocationRegistry - from ..v1_0.models.presentation_exchange import V10PresentationExchange from ..v2_0.messages.pres_format import V20PresFormat from ..v2_0.models.pres_exchange import V20PresExRecord @@ -214,73 +212,3 @@ async def return_presentation( ) indy_proof = json.loads(indy_proof_json) return indy_proof - - async def process_pres_identifiers( - self, - identifiers: list, - ) -> Tuple[dict, dict, dict, dict]: - """Return schemas, cred_defs, rev_reg_defs, rev_reg_entries.""" - schema_ids = [] - cred_def_ids = [] - - schemas = {} - cred_defs = {} - rev_reg_defs = {} - rev_reg_entries = {} - - for identifier in identifiers: - schema_ids.append(identifier["schema_id"]) - cred_def_ids.append(identifier["cred_def_id"]) - 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( - identifier["schema_id"], - txn_record_type=GET_SCHEMA, - ) - )[1] - async with ledger: - # Build schemas for anoncreds - if identifier["schema_id"] not in schemas: - schemas[identifier["schema_id"]] = await ledger.get_schema( - identifier["schema_id"] - ) - - if identifier["cred_def_id"] not in cred_defs: - cred_defs[ - identifier["cred_def_id"] - ] = await ledger.get_credential_definition( - identifier["cred_def_id"] - ) - - if identifier.get("rev_reg_id"): - if identifier["rev_reg_id"] not in rev_reg_defs: - rev_reg_defs[ - identifier["rev_reg_id"] - ] = await ledger.get_revoc_reg_def(identifier["rev_reg_id"]) - - if identifier.get("timestamp"): - rev_reg_entries.setdefault(identifier["rev_reg_id"], {}) - - if ( - identifier["timestamp"] - not in rev_reg_entries[identifier["rev_reg_id"]] - ): - ( - found_rev_reg_entry, - _found_timestamp, - ) = await ledger.get_revoc_reg_entry( - identifier["rev_reg_id"], identifier["timestamp"] - ) - rev_reg_entries[identifier["rev_reg_id"]][ - identifier["timestamp"] - ] = found_rev_reg_entry - return ( - schemas, - cred_defs, - rev_reg_defs, - rev_reg_entries, - ) diff --git a/aries_cloudagent/protocols/present_proof/v1_0/manager.py b/aries_cloudagent/protocols/present_proof/v1_0/manager.py index 376d69f93c..208483a2c8 100644 --- a/aries_cloudagent/protocols/present_proof/v1_0/manager.py +++ b/aries_cloudagent/protocols/present_proof/v1_0/manager.py @@ -410,13 +410,13 @@ async def verify_presentation( """ indy_proof_request = presentation_exchange_record._presentation_request.ser indy_proof = presentation_exchange_record._presentation.ser - indy_handler = IndyPresExchHandler(self._profile) + verifier = self._profile.inject(AnonCredsVerifier) ( schemas, cred_defs, rev_reg_defs, - rev_reg_entries, - ) = await indy_handler.process_pres_identifiers(indy_proof["identifiers"]) + rev_status_lists, + ) = await verifier.process_pres_identifiers(indy_proof["identifiers"]) verifier = self._profile.inject(AnonCredsVerifier) (verified_bool, verified_msgs) = await verifier.verify_presentation( @@ -427,7 +427,7 @@ async def verify_presentation( schemas, cred_defs, rev_reg_defs, - rev_reg_entries, + rev_status_lists, ) presentation_exchange_record.verified = json.dumps(verified_bool) presentation_exchange_record.verified_msgs = list(set(verified_msgs)) diff --git a/aries_cloudagent/protocols/present_proof/v2_0/formats/indy/handler.py b/aries_cloudagent/protocols/present_proof/v2_0/formats/indy/handler.py index 802a928bbd..0c2dbe6be4 100644 --- a/aries_cloudagent/protocols/present_proof/v2_0/formats/indy/handler.py +++ b/aries_cloudagent/protocols/present_proof/v2_0/formats/indy/handler.py @@ -320,13 +320,13 @@ 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(IndyPresExchangeHandler.format) indy_proof = pres_ex_record.pres.attachment(IndyPresExchangeHandler.format) - indy_handler = IndyPresExchHandler(self._profile) + verifier = self._profile.inject(AnonCredsVerifier) ( schemas, cred_defs, rev_reg_defs, - rev_reg_entries, - ) = await indy_handler.process_pres_identifiers(indy_proof["identifiers"]) + rev_status_lists, + ) = await verifier.process_pres_identifiers(indy_proof["identifiers"]) verifier = self._profile.inject(AnonCredsVerifier) (verified, verified_msgs) = await verifier.verify_presentation( @@ -335,7 +335,7 @@ async def verify_pres(self, pres_ex_record: V20PresExRecord) -> V20PresExRecord: schemas, cred_defs, rev_reg_defs, - rev_reg_entries, + rev_status_lists, ) pres_ex_record.verified = json.dumps(verified) pres_ex_record.verified_msgs = list(set(verified_msgs)) From 27596c9ec4961bc09b13d9392119b34c0dcac88e Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Wed, 29 Mar 2023 17:57:23 -0400 Subject: [PATCH 086/150] refactor: split up indy pres exch handler return_presentation Signed-off-by: Daniel Bluhm --- .../present_proof/indy/pres_exch_handler.py | 108 ++++++++++++++---- 1 file changed, 87 insertions(+), 21 deletions(-) diff --git a/aries_cloudagent/protocols/present_proof/indy/pres_exch_handler.py b/aries_cloudagent/protocols/present_proof/indy/pres_exch_handler.py index 07ebb01bd2..fa6f7f66c6 100644 --- a/aries_cloudagent/protocols/present_proof/indy/pres_exch_handler.py +++ b/aries_cloudagent/protocols/present_proof/indy/pres_exch_handler.py @@ -36,45 +36,61 @@ def __init__( """Initialize PresExchange Handler.""" super().__init__() self._profile = profile + self.holder = profile.inject(AnonCredsHolder) - async def return_presentation( + def _extract_proof_request(self, pres_ex_record): + if isinstance(pres_ex_record, V20PresExRecord): + return pres_ex_record.pres_request.attachment(V20PresFormat.Format.INDY) + elif isinstance(pres_ex_record, V10PresentationExchange): + return pres_ex_record._presentation_request.ser + + raise TypeError( + "pres_ex_record must be V10PresentationExchange or V20PresExRecord" + ) + + def _get_requested_referents( self, - pres_ex_record: Union[V10PresentationExchange, V20PresExRecord], - requested_credentials: dict = {}, + proof_request: dict, + requested_credentials: dict, + non_revoc_intervals: dict, ) -> dict: - """Return Indy proof request as dict.""" - # Get all credentials for this presentation - holder = self._profile.inject(AnonCredsHolder) - credentials = {} + """Get requested referents for a proof request and requested credentials. + + Returns a dictionary that looks like: + { + "referent-0": {"cred_id": "0", "non_revoked": {"from": ..., "to": ...}}, + "referent-1": {"cred_id": "1", "non_revoked": {"from": ..., "to": ...}} + } + """ - # extract credential ids and non_revoked requested_referents = {} - if isinstance(pres_ex_record, V20PresExRecord): - proof_request = pres_ex_record.pres_request.attachment( - V20PresFormat.Format.INDY - ) - elif isinstance(pres_ex_record, V10PresentationExchange): - proof_request = pres_ex_record._presentation_request.ser - non_revoc_intervals = indy_proof_req2non_revoc_intervals(proof_request) attr_creds = requested_credentials.get("requested_attributes", {}) req_attrs = proof_request.get("requested_attributes", {}) for reft in attr_creds: requested_referents[reft] = {"cred_id": attr_creds[reft]["cred_id"]} if reft in req_attrs and reft in non_revoc_intervals: requested_referents[reft]["non_revoked"] = non_revoc_intervals[reft] + pred_creds = requested_credentials.get("requested_predicates", {}) req_preds = proof_request.get("requested_predicates", {}) for reft in pred_creds: requested_referents[reft] = {"cred_id": pred_creds[reft]["cred_id"]} if reft in req_preds and reft in non_revoc_intervals: requested_referents[reft]["non_revoked"] = non_revoc_intervals[reft] + return requested_referents + + async def _get_credentials(self, requested_referents: dict): # extract mapping of presentation referents to credential ids + credentials = {} for reft in requested_referents: credential_id = requested_referents[reft]["cred_id"] if credential_id not in credentials: credentials[credential_id] = json.loads( - await holder.get_credential(credential_id) + await self.holder.get_credential(credential_id) ) + return credentials + + def _remove_superfluous_timestamps(self, requested_credentials, credentials): # remove any timestamps that cannot correspond to non-revoc intervals for r in ("requested_attributes", "requested_predicates"): for reft, req_item in requested_credentials.get(r, {}).items(): @@ -85,6 +101,8 @@ async def return_presentation( f"Removed superfluous timestamp from requested_credentials {r} " f"{reft} for non-revocable credential {req_item['cred_id']}" ) + + async def _get_ledger_objects(self, credentials: dict): # Get all schemas, credential definitions, and revocation registries in use schemas = {} cred_defs = {} @@ -119,8 +137,16 @@ async def return_presentation( ] = RevocationRegistry.from_definition( await ledger.get_revoc_reg_def(revocation_registry_id), True ) - # Get delta with non-revocation interval defined in "non_revoked" - # of the presentation request or attributes + return schemas, cred_defs, revocation_registries + + async def _get_revocation_registries_deltas( + self, requested_referents: dict, credentials: dict + ): + """Get deltas. + + Get delta with non-revocation interval defined in "non_revoked" + of the presentation request or attributes + """ epoch_now = int(time.time()) revoc_reg_deltas = {} for precis in requested_referents.values(): # cred_id, non-revoc interval @@ -165,7 +191,13 @@ async def return_presentation( # often one cred satisfies many requested attrs/preds if stamp_me["cred_id"] == credential_id: stamp_me["timestamp"] = revoc_reg_deltas[key][3] - # Get revocation states to prove non-revoked + + return revoc_reg_deltas + + async def _get_revocation_states( + self, revocation_registries: dict, credentials: dict, revoc_reg_deltas: dict + ): + """Get revocation states to prove non-revoked.""" revocation_states = {} for ( rev_reg_id, @@ -179,7 +211,7 @@ async def return_presentation( tails_local_path = await rev_reg.get_or_fetch_local_tails_path() try: revocation_states[rev_reg_id][delta_timestamp] = json.loads( - await holder.create_revocation_state( + await self.holder.create_revocation_state( credentials[credential_id]["cred_rev_id"], rev_reg.reg_def, delta, @@ -192,6 +224,9 @@ async def return_presentation( f"Failed to create revocation state: {e.error_code}, {e.message}" ) raise e + return revocation_states + + def _set_timestamps(self, requested_credentials: dict, requested_referents: dict): for referent, precis in requested_referents.items(): if "timestamp" not in precis: continue @@ -203,7 +238,38 @@ async def return_presentation( requested_credentials["requested_predicates"][referent][ "timestamp" ] = precis["timestamp"] - indy_proof_json = await holder.create_presentation( + + async def return_presentation( + self, + pres_ex_record: Union[V10PresentationExchange, V20PresExRecord], + requested_credentials: dict = {}, + ) -> dict: + """Return Indy proof request as dict.""" + proof_request = self._extract_proof_request(pres_ex_record) + non_revoc_intervals = indy_proof_req2non_revoc_intervals(proof_request) + + requested_referents = self._get_requested_referents( + proof_request, requested_credentials, non_revoc_intervals + ) + + credentials = await self._get_credentials(requested_referents) + self._remove_superfluous_timestamps(requested_credentials, credentials) + + schemas, cred_defs, revocation_registries = await self._get_ledger_objects( + credentials + ) + + revoc_reg_deltas = await self._get_revocation_registries_deltas( + requested_referents, credentials + ) + + revocation_states = await self._get_revocation_states( + revocation_registries, credentials, revoc_reg_deltas + ) + + self._set_timestamps(requested_credentials, requested_referents) + + indy_proof_json = await self.holder.create_presentation( proof_request, requested_credentials, schemas, From ab81356ea7e853ac80b7c30fb13c17fcad220ae4 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Wed, 29 Mar 2023 19:29:24 -0400 Subject: [PATCH 087/150] feat: use anoncreds holder Signed-off-by: Daniel Bluhm --- .../anoncreds/anoncreds/holder.py | 29 ++-- aries_cloudagent/anoncreds/holder.py | 7 +- aries_cloudagent/askar/profile.py | 2 +- .../v2_0/formats/indy/handler.py | 65 +++----- .../present_proof/indy/pres_exch_handler.py | 151 ++++++++---------- 5 files changed, 113 insertions(+), 141 deletions(-) diff --git a/aries_cloudagent/anoncreds/anoncreds/holder.py b/aries_cloudagent/anoncreds/anoncreds/holder.py index 05e6cfd30f..e1ffc1b246 100644 --- a/aries_cloudagent/anoncreds/anoncreds/holder.py +++ b/aries_cloudagent/anoncreds/anoncreds/holder.py @@ -19,6 +19,8 @@ PresentCredentials, ) +from ..models.anoncreds_cred_def import CredDef + from ...askar.profile import AskarProfile from ...ledger.base import BaseLedger from ...wallet.error import WalletNotFoundError @@ -48,14 +50,14 @@ def _normalize_attr_name(name: str) -> str: return name.replace(" ", "") -class IndyCredxHolder(AnonCredsHolder): - """Indy-credx holder class.""" +class AnonCredsRsHolder(AnonCredsHolder): + """AnonCreds holder class.""" MASTER_SECRET_ID = "default" def __init__(self, profile: AskarProfile): """ - Initialize an IndyCredxHolder instance. + Initialize an AnonCredsRsHolder instance. Args: profile: The active profile instance @@ -75,7 +77,7 @@ async def get_master_secret(self) -> MasterSecret: async with self._profile.session() as session: try: record = await session.handle.fetch( - CATEGORY_MASTER_SECRET, IndyCredxHolder.MASTER_SECRET_ID + CATEGORY_MASTER_SECRET, AnonCredsRsHolder.MASTER_SECRET_ID ) except AskarError as err: raise AnonCredsHolderError("Error fetching master secret") from err @@ -97,7 +99,7 @@ async def get_master_secret(self) -> MasterSecret: try: await session.handle.insert( CATEGORY_MASTER_SECRET, - IndyCredxHolder.MASTER_SECRET_ID, + AnonCredsRsHolder.MASTER_SECRET_ID, secret.to_json_buffer(), ) except AskarError as err: @@ -111,7 +113,7 @@ async def get_master_secret(self) -> MasterSecret: return secret async def create_credential_request( - self, credential_offer: dict, credential_definition: dict, holder_did: str + self, credential_offer: dict, credential_definition: CredDef, holder_did: str ) -> Tuple[str, str]: """ Create a credential request for the given credential offer. @@ -133,10 +135,11 @@ async def create_credential_request( ) = await asyncio.get_event_loop().run_in_executor( None, CredentialRequest.create, + None, holder_did, - credential_definition, + credential_definition.to_native(), secret, - IndyCredxHolder.MASTER_SECRET_ID, + AnonCredsRsHolder.MASTER_SECRET_ID, credential_offer, ) except AnoncredsError as err: @@ -543,8 +546,8 @@ def get_rev_state(cred_id: str, detail: dict): present_creds, self_attest, secret, - schemas.values(), - credential_definitions.values(), + schemas, + credential_definitions, ) except AnoncredsError as err: raise AnonCredsHolderError("Error creating presentation") from err @@ -555,8 +558,7 @@ async def create_revocation_state( self, cred_rev_id: str, rev_reg_def: dict, - rev_reg_delta: dict, - timestamp: int, + rev_status_list: dict, tails_file_path: str, ) -> str: """ @@ -578,9 +580,8 @@ async def create_revocation_state( None, CredentialRevocationState.create, rev_reg_def, - rev_reg_delta, + rev_status_list, int(cred_rev_id), - timestamp, tails_file_path, ) except AnoncredsError as err: diff --git a/aries_cloudagent/anoncreds/holder.py b/aries_cloudagent/anoncreds/holder.py index 654e6f67f8..b7aa912737 100644 --- a/aries_cloudagent/anoncreds/holder.py +++ b/aries_cloudagent/anoncreds/holder.py @@ -3,6 +3,8 @@ from abc import ABC, ABCMeta, abstractmethod from typing import Optional, Sequence, Tuple, Union +from .models.anoncreds_cred_def import CredDef + from ..core.error import BaseError from ..ledger.base import BaseLedger @@ -130,7 +132,7 @@ async def create_presentation( @abstractmethod async def create_credential_request( - self, credential_offer: dict, credential_definition: dict, holder_did: str + self, credential_offer: dict, credential_definition: CredDef, holder_did: str ) -> Tuple[str, str]: """ Create a credential request for the given credential offer. @@ -178,8 +180,7 @@ async def create_revocation_state( self, cred_rev_id: str, rev_reg_def: dict, - rev_reg_delta: dict, - timestamp: int, + rev_status_list: dict, tails_file_path: str, ) -> str: """ diff --git a/aries_cloudagent/askar/profile.py b/aries_cloudagent/askar/profile.py index fc89377aba..8529b3864c 100644 --- a/aries_cloudagent/askar/profile.py +++ b/aries_cloudagent/askar/profile.py @@ -103,7 +103,7 @@ def bind_providers(self): injector.bind_provider( AnonCredsHolder, ClassProvider( - "aries_cloudagent.anoncreds.credx.holder.IndyCredxHolder", + "aries_cloudagent.anoncreds.anoncreds.holder.AnonCredsRsHolder", ref(self), ), ) diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/handler.py b/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/handler.py index 27f1b59309..19abd4602a 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/handler.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/handler.py @@ -1,21 +1,22 @@ """V2.0 issue-credential indy credential format handler.""" +import asyncio +import json import logging +from typing import Mapping, Tuple from marshmallow import RAISE -import json -from typing import Mapping, Tuple -import asyncio -from ......cache.base import BaseCache +from ......anoncreds.anoncreds.anoncreds_registry import AnonCredsRegistry +from ......anoncreds.holder import AnonCredsHolder, AnonCredsHolderError from ......anoncreds.issuer import ( AnonCredsIssuer, AnonCredsIssuerRevocationRegistryFullError, ) -from ......anoncreds.holder import AnonCredsHolder, AnonCredsHolderError from ......anoncreds.models.cred import IndyCredentialSchema -from ......anoncreds.models.cred_request import IndyCredRequestSchema from ......anoncreds.models.cred_abstract import IndyCredAbstractSchema +from ......anoncreds.models.cred_request import IndyCredRequestSchema +from ......cache.base import BaseCache from ......ledger.base import BaseLedger from ......ledger.multiple_ledger.ledger_requests_executor import ( GET_CRED_DEF, @@ -31,7 +32,6 @@ from ......revocation.models.issuer_cred_rev_record import IssuerCredRevRecord from ......revocation.models.revocation_registry import RevocationRegistry from ......storage.base import BaseStorage - from ...message_types import ( ATTACHMENT_FORMAT, CRED_20_ISSUE, @@ -40,13 +40,12 @@ CRED_20_REQUEST, ) from ...messages.cred_format import V20CredFormat -from ...messages.cred_proposal import V20CredProposal +from ...messages.cred_issue import V20CredIssue from ...messages.cred_offer import V20CredOffer +from ...messages.cred_proposal import V20CredProposal from ...messages.cred_request import V20CredRequest -from ...messages.cred_issue import V20CredIssue from ...models.cred_ex_record import V20CredExRecord from ...models.detail.indy import V20CredExRecordIndy - from ..handler import CredFormatAttachment, V20CredFormatError, V20CredFormatHandler LOGGER = logging.getLogger(__name__) @@ -267,23 +266,15 @@ async def create_request( 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) + anoncreds_registry = self.profile.inject(AnonCredsRegistry) + + cred_def_result = await anoncreds_registry.get_credential_definition( + self.profile, cred_def_id + ) holder = self.profile.inject(AnonCredsHolder) request_json, metadata_json = await holder.create_credential_request( - cred_offer, cred_def, holder_did + cred_offer, cred_def_result.credential_definition, holder_did ) return { @@ -433,21 +424,17 @@ async def store_credential( cred = cred_ex_record.cred_issue.attachment(IndyCredFormatHandler.format) rev_reg_def = None - 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["cred_def_id"], - txn_record_type=GET_CRED_DEF, + 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"] + ) ) - )[1] - async with ledger: - cred_def = await ledger.get_credential_definition(cred["cred_def_id"]) - if cred.get("rev_reg_id"): - rev_reg_def = await ledger.get_revoc_reg_def(cred["rev_reg_id"]) + rev_reg_def = rev_reg_def_result.revocation_registry.serialize() holder = self.profile.inject(AnonCredsHolder) cred_offer_message = cred_ex_record.cred_offer @@ -466,7 +453,7 @@ async def store_credential( f"detail record found for cred ex id {cred_ex_record.cred_ex_id}" ) cred_id_stored = await holder.store_credential( - cred_def, + cred_def_result.credential_definition.serialize(), cred, detail_record.cred_request_metadata, mime_types, diff --git a/aries_cloudagent/protocols/present_proof/indy/pres_exch_handler.py b/aries_cloudagent/protocols/present_proof/indy/pres_exch_handler.py index fa6f7f66c6..928bf4d155 100644 --- a/aries_cloudagent/protocols/present_proof/indy/pres_exch_handler.py +++ b/aries_cloudagent/protocols/present_proof/indy/pres_exch_handler.py @@ -4,16 +4,11 @@ import time from typing import Union +from ....anoncreds.anoncreds.anoncreds_registry import AnonCredsRegistry from ....anoncreds.holder import AnonCredsHolder, AnonCredsHolderError from ....anoncreds.models.xform import indy_proof_req2non_revoc_intervals from ....core.error import BaseError from ....core.profile import Profile -from ....ledger.multiple_ledger.ledger_requests_executor import ( - GET_REVOC_REG_DELTA, - GET_SCHEMA, - IndyLedgerRequestsExecutor, -) -from ....multitenant.base import BaseMultitenantManager from ....revocation.models.revocation_registry import RevocationRegistry from ..v1_0.models.presentation_exchange import V10PresentationExchange from ..v2_0.messages.pres_format import V20PresFormat @@ -80,7 +75,7 @@ def _get_requested_referents( return requested_referents async def _get_credentials(self, requested_referents: dict): - # extract mapping of presentation referents to credential ids + """Extract mapping of presentation referents to credential ids""" credentials = {} for reft in requested_referents: credential_id = requested_referents[reft]["cred_id"] @@ -91,7 +86,7 @@ async def _get_credentials(self, requested_referents: dict): return credentials def _remove_superfluous_timestamps(self, requested_credentials, credentials): - # remove any timestamps that cannot correspond to non-revoc intervals + """Remove any timestamps that cannot correspond to non-revoc intervals.""" for r in ("requested_attributes", "requested_predicates"): for reft, req_item in requested_credentials.get(r, {}).items(): if not credentials[req_item["cred_id"]].get( @@ -103,52 +98,50 @@ def _remove_superfluous_timestamps(self, requested_credentials, credentials): ) async def _get_ledger_objects(self, credentials: dict): - # Get all schemas, credential definitions, and revocation registries in use + """Get all schemas, credential definitions, and revocation registries in use""" schemas = {} cred_defs = {} revocation_registries = {} for credential in credentials.values(): schema_id = credential["schema_id"] - 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( - schema_id, - txn_record_type=GET_SCHEMA, - ) - )[1] - async with ledger: - if schema_id not in schemas: - schemas[schema_id] = await ledger.get_schema(schema_id) - cred_def_id = credential["cred_def_id"] - if cred_def_id not in cred_defs: - cred_defs[cred_def_id] = await ledger.get_credential_definition( - cred_def_id + anoncreds_registry = self._profile.inject(AnonCredsRegistry) + if schema_id not in schemas: + schemas[schema_id] = ( + await anoncreds_registry.get_schema(self._profile, schema_id) + ).schema.serialize() + cred_def_id = credential["cred_def_id"] + if cred_def_id not in cred_defs: + cred_defs[cred_def_id] = ( + await anoncreds_registry.get_credential_definition( + self._profile, cred_def_id + ) + ).credential_definition.serialize() + if credential.get("rev_reg_id"): + revocation_registry_id = credential["rev_reg_id"] + if revocation_registry_id not in revocation_registries: + revocation_registries[ + revocation_registry_id + ] = RevocationRegistry.from_definition( + ( + await anoncreds_registry.get_revocation_registry_definition( + self._profile, revocation_registry_id + ) + ).revocation_registry.serialize(), + True, ) - if credential.get("rev_reg_id"): - revocation_registry_id = credential["rev_reg_id"] - if revocation_registry_id not in revocation_registries: - revocation_registries[ - revocation_registry_id - ] = RevocationRegistry.from_definition( - await ledger.get_revoc_reg_def(revocation_registry_id), True - ) return schemas, cred_defs, revocation_registries - async def _get_revocation_registries_deltas( + async def _get_revocation_status_lists( self, requested_referents: dict, credentials: dict ): - """Get deltas. + """Get revocation status lists. - Get delta with non-revocation interval defined in "non_revoked" - of the presentation request or attributes + Get revocation status lists with non-revocation interval defined in + "non_revoked" of the presentation request or attributes """ epoch_now = int(time.time()) - revoc_reg_deltas = {} + rev_status_lists = {} for precis in requested_referents.values(): # cred_id, non-revoc interval credential_id = precis["cred_id"] if not credentials[credential_id].get("rev_reg_id"): @@ -156,66 +149,56 @@ async def _get_revocation_registries_deltas( if "timestamp" in precis: continue rev_reg_id = credentials[credential_id]["rev_reg_id"] - 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( - rev_reg_id, - txn_record_type=GET_REVOC_REG_DELTA, + + anoncreds_registry = self._profile.inject(AnonCredsRegistry) + reft_non_revoc_interval = precis.get("non_revoked") + if reft_non_revoc_interval: + key = ( + f"{rev_reg_id}_" + f"{reft_non_revoc_interval.get('from', 0)}_" + f"{reft_non_revoc_interval.get('to', epoch_now)}" ) - )[1] - async with ledger: - reft_non_revoc_interval = precis.get("non_revoked") - if reft_non_revoc_interval: - key = ( - f"{rev_reg_id}_" - f"{reft_non_revoc_interval.get('from', 0)}_" - f"{reft_non_revoc_interval.get('to', epoch_now)}" + if key not in rev_status_lists: + result = await anoncreds_registry.get_revocation_status_list( + self._profile, + rev_reg_id, + reft_non_revoc_interval.get("to", epoch_now), ) - if key not in revoc_reg_deltas: - (delta, delta_timestamp) = await ledger.get_revoc_reg_delta( - rev_reg_id, - reft_non_revoc_interval.get("from", 0), - reft_non_revoc_interval.get("to", epoch_now), - ) - revoc_reg_deltas[key] = ( - rev_reg_id, - credential_id, - delta, - delta_timestamp, - ) - for stamp_me in requested_referents.values(): - # often one cred satisfies many requested attrs/preds - if stamp_me["cred_id"] == credential_id: - stamp_me["timestamp"] = revoc_reg_deltas[key][3] - - return revoc_reg_deltas + + rev_status_lists[key] = ( + rev_reg_id, + credential_id, + result.revocation_list.serialize(), + result.revocation_list.timestamp, + ) + for stamp_me in requested_referents.values(): + # often one cred satisfies many requested attrs/preds + if stamp_me["cred_id"] == credential_id: + stamp_me["timestamp"] = rev_status_lists[key][3] + + return rev_status_lists async def _get_revocation_states( - self, revocation_registries: dict, credentials: dict, revoc_reg_deltas: dict + self, revocation_registries: dict, credentials: dict, rev_status_lists: dict ): """Get revocation states to prove non-revoked.""" revocation_states = {} for ( rev_reg_id, credential_id, - delta, - delta_timestamp, - ) in revoc_reg_deltas.values(): + rev_status_list, + timestamp, + ) in rev_status_lists.values(): if rev_reg_id not in revocation_states: revocation_states[rev_reg_id] = {} rev_reg = revocation_registries[rev_reg_id] tails_local_path = await rev_reg.get_or_fetch_local_tails_path() try: - revocation_states[rev_reg_id][delta_timestamp] = json.loads( + revocation_states[rev_reg_id][timestamp] = json.loads( await self.holder.create_revocation_state( credentials[credential_id]["cred_rev_id"], rev_reg.reg_def, - delta, - delta_timestamp, + rev_status_list, tails_local_path, ) ) @@ -259,12 +242,12 @@ async def return_presentation( credentials ) - revoc_reg_deltas = await self._get_revocation_registries_deltas( + rev_status_lists = await self._get_revocation_status_lists( requested_referents, credentials ) revocation_states = await self._get_revocation_states( - revocation_registries, credentials, revoc_reg_deltas + revocation_registries, credentials, rev_status_lists ) self._set_timestamps(requested_credentials, requested_referents) From 74043f574ebc7ef208da0f6f7cd28b0d812ec9b2 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Thu, 30 Mar 2023 09:35:32 -0600 Subject: [PATCH 088/150] feat: restore indy package Signed-off-by: Char Howland --- aries_cloudagent/indy/__init__.py | 0 aries_cloudagent/indy/credx/__init__.py | 0 aries_cloudagent/indy/credx/holder.py | 576 +++++++++++ aries_cloudagent/indy/credx/issuer.py | 621 ++++++++++++ aries_cloudagent/indy/credx/tests/__init__.py | 0 .../indy/credx/tests/test_cred_issuance.py | 341 +++++++ aries_cloudagent/indy/credx/verifier.py | 85 ++ aries_cloudagent/indy/holder.py | 164 ++++ aries_cloudagent/indy/issuer.py | 204 ++++ aries_cloudagent/indy/models/__init__.py | 0 aries_cloudagent/indy/models/cred.py | 129 +++ aries_cloudagent/indy/models/cred_abstract.py | 130 +++ aries_cloudagent/indy/models/cred_def.py | 83 ++ aries_cloudagent/indy/models/cred_precis.py | 100 ++ aries_cloudagent/indy/models/cred_request.py | 68 ++ .../indy/models/non_rev_interval.py | 70 ++ aries_cloudagent/indy/models/predicate.py | 84 ++ aries_cloudagent/indy/models/pres_preview.py | 477 +++++++++ aries_cloudagent/indy/models/proof.py | 676 +++++++++++++ aries_cloudagent/indy/models/proof_request.py | 260 +++++ .../indy/models/requested_creds.py | 39 + aries_cloudagent/indy/models/revocation.py | 272 ++++++ aries_cloudagent/indy/models/schema.py | 27 + .../indy/models/tests/__init__.py | 0 .../indy/models/tests/test_cred.py | 126 +++ .../indy/models/tests/test_cred_precis.py | 43 + .../models/tests/test_non_rev_interval.py | 44 + .../indy/models/tests/test_pred.py | 49 + .../indy/models/tests/test_pres_preview.py | 586 +++++++++++ .../indy/models/tests/test_proof.py | 213 ++++ .../indy/models/tests/test_proof_request.py | 89 ++ aries_cloudagent/indy/models/xform.py | 130 +++ aries_cloudagent/indy/sdk/__init__.py | 0 aries_cloudagent/indy/sdk/error.py | 43 + aries_cloudagent/indy/sdk/holder.py | 483 +++++++++ aries_cloudagent/indy/sdk/issuer.py | 385 ++++++++ aries_cloudagent/indy/sdk/profile.py | 196 ++++ aries_cloudagent/indy/sdk/tests/__init__.py | 0 .../indy/sdk/tests/test_holder.py | 604 ++++++++++++ .../indy/sdk/tests/test_issuer.py | 391 ++++++++ .../indy/sdk/tests/test_profile.py | 86 ++ aries_cloudagent/indy/sdk/tests/test_util.py | 46 + .../indy/sdk/tests/test_verifier.py | 596 ++++++++++++ .../indy/sdk/tests/test_wallet_plugin.py | 129 +++ aries_cloudagent/indy/sdk/util.py | 29 + aries_cloudagent/indy/sdk/verifier.py | 88 ++ aries_cloudagent/indy/sdk/wallet_plugin.py | 63 ++ aries_cloudagent/indy/sdk/wallet_setup.py | 236 +++++ aries_cloudagent/indy/tests/__init__.py | 0 aries_cloudagent/indy/tests/test_verifier.py | 921 ++++++++++++++++++ aries_cloudagent/indy/util.py | 39 + aries_cloudagent/indy/verifier.py | 411 ++++++++ 52 files changed, 10432 insertions(+) create mode 100644 aries_cloudagent/indy/__init__.py create mode 100644 aries_cloudagent/indy/credx/__init__.py create mode 100644 aries_cloudagent/indy/credx/holder.py create mode 100644 aries_cloudagent/indy/credx/issuer.py create mode 100644 aries_cloudagent/indy/credx/tests/__init__.py create mode 100644 aries_cloudagent/indy/credx/tests/test_cred_issuance.py create mode 100644 aries_cloudagent/indy/credx/verifier.py create mode 100644 aries_cloudagent/indy/holder.py create mode 100644 aries_cloudagent/indy/issuer.py create mode 100644 aries_cloudagent/indy/models/__init__.py create mode 100644 aries_cloudagent/indy/models/cred.py create mode 100644 aries_cloudagent/indy/models/cred_abstract.py create mode 100644 aries_cloudagent/indy/models/cred_def.py create mode 100644 aries_cloudagent/indy/models/cred_precis.py create mode 100644 aries_cloudagent/indy/models/cred_request.py create mode 100644 aries_cloudagent/indy/models/non_rev_interval.py create mode 100644 aries_cloudagent/indy/models/predicate.py create mode 100644 aries_cloudagent/indy/models/pres_preview.py create mode 100644 aries_cloudagent/indy/models/proof.py create mode 100644 aries_cloudagent/indy/models/proof_request.py create mode 100644 aries_cloudagent/indy/models/requested_creds.py create mode 100644 aries_cloudagent/indy/models/revocation.py create mode 100644 aries_cloudagent/indy/models/schema.py create mode 100644 aries_cloudagent/indy/models/tests/__init__.py create mode 100644 aries_cloudagent/indy/models/tests/test_cred.py create mode 100644 aries_cloudagent/indy/models/tests/test_cred_precis.py create mode 100644 aries_cloudagent/indy/models/tests/test_non_rev_interval.py create mode 100644 aries_cloudagent/indy/models/tests/test_pred.py create mode 100644 aries_cloudagent/indy/models/tests/test_pres_preview.py create mode 100644 aries_cloudagent/indy/models/tests/test_proof.py create mode 100644 aries_cloudagent/indy/models/tests/test_proof_request.py create mode 100644 aries_cloudagent/indy/models/xform.py create mode 100644 aries_cloudagent/indy/sdk/__init__.py create mode 100644 aries_cloudagent/indy/sdk/error.py create mode 100644 aries_cloudagent/indy/sdk/holder.py create mode 100644 aries_cloudagent/indy/sdk/issuer.py create mode 100644 aries_cloudagent/indy/sdk/profile.py create mode 100644 aries_cloudagent/indy/sdk/tests/__init__.py create mode 100644 aries_cloudagent/indy/sdk/tests/test_holder.py create mode 100644 aries_cloudagent/indy/sdk/tests/test_issuer.py create mode 100644 aries_cloudagent/indy/sdk/tests/test_profile.py create mode 100644 aries_cloudagent/indy/sdk/tests/test_util.py create mode 100644 aries_cloudagent/indy/sdk/tests/test_verifier.py create mode 100644 aries_cloudagent/indy/sdk/tests/test_wallet_plugin.py create mode 100644 aries_cloudagent/indy/sdk/util.py create mode 100644 aries_cloudagent/indy/sdk/verifier.py create mode 100644 aries_cloudagent/indy/sdk/wallet_plugin.py create mode 100644 aries_cloudagent/indy/sdk/wallet_setup.py create mode 100644 aries_cloudagent/indy/tests/__init__.py create mode 100644 aries_cloudagent/indy/tests/test_verifier.py create mode 100644 aries_cloudagent/indy/util.py create mode 100644 aries_cloudagent/indy/verifier.py diff --git a/aries_cloudagent/indy/__init__.py b/aries_cloudagent/indy/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aries_cloudagent/indy/credx/__init__.py b/aries_cloudagent/indy/credx/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aries_cloudagent/indy/credx/holder.py b/aries_cloudagent/indy/credx/holder.py new file mode 100644 index 0000000000..a212661cbc --- /dev/null +++ b/aries_cloudagent/indy/credx/holder.py @@ -0,0 +1,576 @@ +"""Indy holder implementation.""" + +import asyncio +import json +import logging +import re +import uuid + +from typing import Dict, Sequence, Tuple, Union + +from aries_askar import AskarError, AskarErrorCode +from indy_credx import ( + CredxError, + Credential, + CredentialRequest, + CredentialRevocationState, + MasterSecret, + Presentation, + PresentCredentials, +) + +from ...askar.profile import AskarProfile +from ...ledger.base import BaseLedger +from ...wallet.error import WalletNotFoundError + +from ..holder import IndyHolder, IndyHolderError + +LOGGER = logging.getLogger(__name__) + +CATEGORY_CREDENTIAL = "credential" +CATEGORY_MASTER_SECRET = "master_secret" + + +def _make_cred_info(cred_id, cred: Credential): + cred_info = cred.to_dict() # not secure! + rev_info = cred_info["signature"]["r_credential"] + return { + "referent": cred_id, + "schema_id": cred_info["schema_id"], + "cred_def_id": cred_info["cred_def_id"], + "rev_reg_id": cred_info["rev_reg_id"], + "cred_rev_id": str(rev_info["i"]) if rev_info else None, + "attrs": {name: val["raw"] for (name, val) in cred_info["values"].items()}, + } + + +def _normalize_attr_name(name: str) -> str: + return name.replace(" ", "") + + +class IndyCredxHolder(IndyHolder): + """Indy-credx holder class.""" + + MASTER_SECRET_ID = "default" + + def __init__(self, profile: AskarProfile): + """ + Initialize an IndyCredxHolder instance. + + Args: + profile: The active profile instance + + """ + self._profile = profile + + @property + def profile(self) -> AskarProfile: + """Accessor for the profile instance.""" + return self._profile + + async def get_master_secret(self) -> MasterSecret: + """Get or create the default master secret.""" + + while True: + async with self._profile.session() as session: + try: + record = await session.handle.fetch( + CATEGORY_MASTER_SECRET, IndyCredxHolder.MASTER_SECRET_ID + ) + except AskarError as err: + raise IndyHolderError("Error fetching master secret") from err + if record: + try: + secret = MasterSecret.load(record.raw_value) + except CredxError as err: + raise IndyHolderError("Error loading master secret") from err + break + else: + try: + secret = MasterSecret.create() + except CredxError as err: + raise IndyHolderError("Error creating master secret") from err + try: + await session.handle.insert( + CATEGORY_MASTER_SECRET, + IndyCredxHolder.MASTER_SECRET_ID, + secret.to_json_buffer(), + ) + except AskarError as err: + if err.code != AskarErrorCode.DUPLICATE: + raise IndyHolderError("Error saving master secret") from err + # else: lost race to create record, retry + else: + break + return secret + + async def create_credential_request( + self, credential_offer: dict, credential_definition: dict, holder_did: str + ) -> Tuple[str, str]: + """ + Create a credential request for the given credential offer. + + Args: + credential_offer: The credential offer to create request for + credential_definition: The credential definition to create an offer for + holder_did: the DID of the agent making the request + + Returns: + A tuple of the credential request and credential request metadata + + """ + try: + secret = await self.get_master_secret() + ( + cred_req, + cred_req_metadata, + ) = await asyncio.get_event_loop().run_in_executor( + None, + CredentialRequest.create, + holder_did, + credential_definition, + secret, + IndyCredxHolder.MASTER_SECRET_ID, + credential_offer, + ) + except CredxError as err: + raise IndyHolderError("Error creating credential request") from err + cred_req_json, cred_req_metadata_json = ( + cred_req.to_json(), + cred_req_metadata.to_json(), + ) + + LOGGER.debug( + "Created credential request. " + "credential_request_json=%s credential_request_metadata_json=%s", + cred_req_json, + cred_req_metadata_json, + ) + + return cred_req_json, cred_req_metadata_json + + async def store_credential( + self, + credential_definition: dict, + credential_data: dict, + credential_request_metadata: dict, + credential_attr_mime_types: dict = None, + credential_id: str = None, + rev_reg_def: dict = None, + ) -> str: + """ + Store a credential in the wallet. + + Args: + credential_definition: Credential definition for this credential + credential_data: Credential data generated by the issuer + credential_request_metadata: credential request metadata generated + by the issuer + credential_attr_mime_types: dict mapping attribute names to (optional) + MIME types to store as non-secret record, if specified + credential_id: optionally override the stored credential id + rev_reg_def: revocation registry definition in json + + Returns: + the ID of the stored credential + + """ + try: + secret = await self.get_master_secret() + cred = Credential.load(credential_data) + cred_recvd = await asyncio.get_event_loop().run_in_executor( + None, + cred.process, + credential_request_metadata, + secret, + credential_definition, + rev_reg_def, + ) + except CredxError as err: + raise IndyHolderError("Error processing received credential") from err + + schema_id = cred_recvd.schema_id + schema_id_parts = re.match(r"^(\w+):2:([^:]+):([^:]+)$", schema_id) + if not schema_id_parts: + raise IndyHolderError(f"Error parsing credential schema ID: {schema_id}") + cred_def_id = cred_recvd.cred_def_id + cdef_id_parts = re.match(r"^(\w+):3:CL:([^:]+):([^:]+)$", cred_def_id) + if not cdef_id_parts: + raise IndyHolderError( + f"Error parsing credential definition ID: {cred_def_id}" + ) + + credential_id = credential_id or str(uuid.uuid4()) + tags = { + "schema_id": schema_id, + "schema_issuer_did": schema_id_parts[1], + "schema_name": schema_id_parts[2], + "schema_version": schema_id_parts[3], + "issuer_did": cdef_id_parts[1], + "cred_def_id": cred_def_id, + "rev_reg_id": cred_recvd.rev_reg_id or "None", + } + + # FIXME - sdk has some special handling for fully qualified DIDs here + + mime_types = {} + for k, attr_value in credential_data["values"].items(): + attr_name = _normalize_attr_name(k) + # tags[f"attr::{attr_name}::marker"] = "1" + tags[f"attr::{attr_name}::value"] = attr_value["raw"] + if credential_attr_mime_types and k in credential_attr_mime_types: + mime_types[k] = credential_attr_mime_types[k] + + try: + async with self._profile.transaction() as txn: + await txn.handle.insert( + CATEGORY_CREDENTIAL, + credential_id, + cred_recvd.to_json_buffer(), + tags=tags, + ) + if mime_types: + await txn.handle.insert( + IndyHolder.RECORD_TYPE_MIME_TYPES, + credential_id, + value_json=mime_types, + ) + await txn.commit() + except AskarError as err: + raise IndyHolderError("Error storing credential") from err + + return credential_id + + async def get_credentials(self, start: int, count: int, wql: dict): + """ + Get credentials stored in the wallet. + + Args: + start: Starting index + count: Number of records to return + wql: wql query dict + + """ + + result = [] + + try: + rows = self._profile.store.scan( + CATEGORY_CREDENTIAL, + wql, + start, + count, + self._profile.settings.get("wallet.askar_profile"), + ) + async for row in rows: + cred = Credential.load(row.raw_value) + result.append(_make_cred_info(row.name, cred)) + except AskarError as err: + raise IndyHolderError("Error retrieving credentials") from err + except CredxError as err: + raise IndyHolderError("Error loading stored credential") from err + + return result + + async def get_credentials_for_presentation_request_by_referent( + self, + presentation_request: dict, + referents: Sequence[str], + start: int, + count: int, + extra_query: dict = {}, + ): + """ + Get credentials stored in the wallet. + + Args: + presentation_request: Valid presentation request from issuer + referents: Presentation request referents to use to search for creds + start: Starting index + count: Maximum number of records to return + extra_query: wql query dict + + """ + + if not referents: + referents = ( + *presentation_request["requested_attributes"], + *presentation_request["requested_predicates"], + ) + + creds = {} + + for reft in referents: + names = set() + if reft in presentation_request["requested_attributes"]: + attr = presentation_request["requested_attributes"][reft] + if "name" in attr: + names.add(_normalize_attr_name(attr["name"])) + elif "names" in attr: + names.update(_normalize_attr_name(name) for name in attr["names"]) + # for name in names: + # tag_filter[f"attr::{_normalize_attr_name(name)}::marker"] = "1" + restr = attr.get("restrictions") + elif reft in presentation_request["requested_predicates"]: + pred = presentation_request["requested_predicates"][reft] + if "name" in pred: + names.add(_normalize_attr_name(pred["name"])) + # tag_filter[f"attr::{_normalize_attr_name(name)}::marker"] = "1" + restr = pred.get("restrictions") + else: + raise IndyHolderError(f"Unknown presentation request referent: {reft}") + + tag_filter = {"$exist": list(f"attr::{name}::value" for name in names)} + if restr: + # FIXME check if restr is a list or dict? validate WQL format + tag_filter = {"$and": [tag_filter] + restr} + if extra_query: + tag_filter = {"$and": [tag_filter, extra_query]} + + rows = self._profile.store.scan( + CATEGORY_CREDENTIAL, + tag_filter, + start, + count, + self._profile.settings.get("wallet.askar_profile"), + ) + async for row in rows: + if row.name in creds: + creds[row.name]["presentation_referents"].add(reft) + else: + cred_info = _make_cred_info( + row.name, Credential.load(row.raw_value) + ) + creds[row.name] = { + "cred_info": cred_info, + "interval": presentation_request.get("non_revoked"), + "presentation_referents": {reft}, + } + + for cred in creds.values(): + cred["presentation_referents"] = list(cred["presentation_referents"]) + + return list(creds.values()) + + async def get_credential(self, credential_id: str) -> str: + """ + Get a credential stored in the wallet. + + Args: + credential_id: Credential id to retrieve + + """ + cred = await self._get_credential(credential_id) + return json.dumps(_make_cred_info(credential_id, cred)) + + async def _get_credential(self, credential_id: str) -> Credential: + """Get an unencoded Credential instance from the store.""" + try: + async with self._profile.session() as session: + cred = await session.handle.fetch(CATEGORY_CREDENTIAL, credential_id) + except AskarError as err: + raise IndyHolderError("Error retrieving credential") from err + + if not cred: + raise WalletNotFoundError( + f"Credential {credential_id} not found in wallet {self.profile.name}" + ) + + try: + return Credential.load(cred.raw_value) + except CredxError as err: + raise IndyHolderError("Error loading requested credential") from err + + async def credential_revoked( + self, ledger: BaseLedger, credential_id: str, fro: int = None, to: int = None + ) -> bool: + """ + Check ledger for revocation status of credential by cred id. + + Args: + credential_id: Credential id to check + + """ + cred = await self._get_credential(credential_id) + rev_reg_id = cred.rev_reg_id + + if rev_reg_id: + cred_rev_id = cred.rev_reg_index + (rev_reg_delta, _) = await ledger.get_revoc_reg_delta( + rev_reg_id, + fro, + to, + ) + return cred_rev_id in rev_reg_delta["value"].get("revoked", []) + else: + return False + + async def delete_credential(self, credential_id: str): + """ + Remove a credential stored in the wallet. + + Args: + credential_id: Credential id to remove + + """ + try: + async with self._profile.session() as session: + await session.handle.remove(CATEGORY_CREDENTIAL, credential_id) + await session.handle.remove( + IndyHolder.RECORD_TYPE_MIME_TYPES, credential_id + ) + except AskarError as err: + if err.code == AskarErrorCode.NOT_FOUND: + pass + else: + raise IndyHolderError("Error deleting credential") from err + + async def get_mime_type( + self, credential_id: str, attr: str = None + ) -> Union[dict, str]: + """ + Get MIME type per attribute (or for all attributes). + + Args: + credential_id: credential id + attr: attribute of interest or omit for all + + Returns: Attribute MIME type or dict mapping attribute names to MIME types + attr_meta_json = all_meta.tags.get(attr) + + """ + try: + async with self._profile.session() as session: + mime_types_record = await session.handle.fetch( + IndyHolder.RECORD_TYPE_MIME_TYPES, + credential_id, + ) + except AskarError as err: + raise IndyHolderError("Error retrieving credential mime types") from err + values = mime_types_record and mime_types_record.value_json + if values: + return values.get(attr) if attr else values + + async def create_presentation( + self, + presentation_request: dict, + requested_credentials: dict, + schemas: dict, + credential_definitions: dict, + rev_states: dict = None, + ) -> str: + """ + Get credentials stored in the wallet. + + Args: + presentation_request: Valid indy format presentation request + requested_credentials: Indy format requested credentials + schemas: Indy formatted schemas JSON + credential_definitions: Indy formatted credential definitions JSON + rev_states: Indy format revocation states JSON + + """ + + creds: Dict[str, Credential] = {} + + def get_rev_state(cred_id: str, detail: dict): + cred = creds[cred_id] + rev_reg_id = cred.rev_reg_id + timestamp = detail.get("timestamp") if rev_reg_id else None + rev_state = None + if timestamp: + if not rev_states or rev_reg_id not in rev_states: + raise IndyHolderError( + f"No revocation states provided for credential '{cred_id}' " + f"with rev_reg_id '{rev_reg_id}'" + ) + rev_state = rev_states[rev_reg_id].get(timestamp) + if not rev_state: + raise IndyHolderError( + f"No revocation states provided for credential '{cred_id}' " + f"with rev_reg_id '{rev_reg_id}' at timestamp {timestamp}" + ) + return timestamp, rev_state + + self_attest = requested_credentials.get("self_attested_attributes") or {} + present_creds = PresentCredentials() + req_attrs = requested_credentials.get("requested_attributes") or {} + for reft, detail in req_attrs.items(): + cred_id = detail["cred_id"] + if cred_id not in creds: + # NOTE: could be optimized if multiple creds are requested + creds[cred_id] = await self._get_credential(cred_id) + timestamp, rev_state = get_rev_state(cred_id, detail) + present_creds.add_attributes( + creds[cred_id], + reft, + reveal=detail["revealed"], + timestamp=timestamp, + rev_state=rev_state, + ) + req_preds = requested_credentials.get("requested_predicates") or {} + for reft, detail in req_preds.items(): + cred_id = detail["cred_id"] + if cred_id not in creds: + # NOTE: could be optimized if multiple creds are requested + creds[cred_id] = await self._get_credential(cred_id) + timestamp, rev_state = get_rev_state(cred_id, detail) + present_creds.add_predicates( + creds[cred_id], + reft, + timestamp=timestamp, + rev_state=rev_state, + ) + + try: + secret = await self.get_master_secret() + presentation = await asyncio.get_event_loop().run_in_executor( + None, + Presentation.create, + presentation_request, + present_creds, + self_attest, + secret, + schemas.values(), + credential_definitions.values(), + ) + except CredxError as err: + raise IndyHolderError("Error creating presentation") from err + + return presentation.to_json() + + async def create_revocation_state( + self, + cred_rev_id: str, + rev_reg_def: dict, + rev_reg_delta: dict, + timestamp: int, + tails_file_path: str, + ) -> str: + """ + Create current revocation state for a received credential. + + Args: + cred_rev_id: credential revocation id in revocation registry + rev_reg_def: revocation registry definition + rev_reg_delta: revocation delta + timestamp: delta timestamp + + Returns: + the revocation state + + """ + + try: + rev_state = await asyncio.get_event_loop().run_in_executor( + None, + CredentialRevocationState.create, + rev_reg_def, + rev_reg_delta, + int(cred_rev_id), + timestamp, + tails_file_path, + ) + except CredxError as err: + raise IndyHolderError("Error creating revocation state") from err + return rev_state.to_json() diff --git a/aries_cloudagent/indy/credx/issuer.py b/aries_cloudagent/indy/credx/issuer.py new file mode 100644 index 0000000000..b4a4524948 --- /dev/null +++ b/aries_cloudagent/indy/credx/issuer.py @@ -0,0 +1,621 @@ +"""Indy issuer implementation.""" + +import asyncio +import logging + +from typing import Sequence, Tuple + +from aries_askar import AskarError + +from indy_credx import ( + Credential, + CredentialDefinition, + CredentialOffer, + CredentialRevocationConfig, + CredxError, + RevocationRegistry, + RevocationRegistryDefinition, + RevocationRegistryDelta, + Schema, +) + +from ...askar.profile import AskarProfile + +from ..issuer import ( + IndyIssuer, + IndyIssuerError, + IndyIssuerRevocationRegistryFullError, + DEFAULT_CRED_DEF_TAG, + DEFAULT_SIGNATURE_TYPE, +) + +LOGGER = logging.getLogger(__name__) + +CATEGORY_CRED_DEF = "credential_def" +CATEGORY_CRED_DEF_PRIVATE = "credential_def_private" +CATEGORY_CRED_DEF_KEY_PROOF = "credential_def_key_proof" +CATEGORY_SCHEMA = "schema" +CATEGORY_REV_REG = "revocation_reg" +CATEGORY_REV_REG_INFO = "revocation_reg_info" +CATEGORY_REV_REG_DEF = "revocation_reg_def" +CATEGORY_REV_REG_DEF_PRIVATE = "revocation_reg_def_private" +CATEGORY_REV_REG_ISSUER = "revocation_reg_def_issuer" + + +class IndyCredxIssuer(IndyIssuer): + """Indy-Credx issuer class.""" + + def __init__(self, profile: AskarProfile): + """ + Initialize an IndyCredxIssuer instance. + + Args: + profile: The active profile instance + + """ + self._profile = profile + + @property + def profile(self) -> AskarProfile: + """Accessor for the profile instance.""" + return self._profile + + async def create_schema( + self, + origin_did: str, + schema_name: str, + schema_version: str, + attribute_names: Sequence[str], + ) -> Tuple[str, str]: + """ + Create a new credential schema and store it in the wallet. + + Args: + origin_did: the DID issuing the credential definition + schema_name: the schema name + schema_version: the schema version + attribute_names: a sequence of schema attribute names + + Returns: + A tuple of the schema ID and JSON + + """ + try: + schema = Schema.create( + origin_did, schema_name, schema_version, attribute_names + ) + schema_id = schema.id + schema_json = schema.to_json() + async with self._profile.session() as session: + await session.handle.insert(CATEGORY_SCHEMA, schema_id, schema_json) + except CredxError as err: + raise IndyIssuerError("Error creating schema") from err + except AskarError as err: + raise IndyIssuerError("Error storing schema") from err + return (schema_id, schema_json) + + async def credential_definition_in_wallet( + self, credential_definition_id: str + ) -> bool: + """ + Check whether a given credential definition ID is present in the wallet. + + Args: + credential_definition_id: The credential definition ID to check + """ + try: + async with self._profile.session() as session: + return ( + await session.handle.fetch( + CATEGORY_CRED_DEF_PRIVATE, credential_definition_id + ) + ) is not None + except AskarError as err: + raise IndyIssuerError("Error checking for credential definition") from err + + async def create_and_store_credential_definition( + self, + origin_did: str, + schema: dict, + signature_type: str = None, + tag: str = None, + support_revocation: bool = False, + ) -> Tuple[str, str]: + """ + Create a new credential definition and store it in the wallet. + + Args: + origin_did: the DID issuing the credential definition + schema_json: the schema used as a basis + signature_type: the credential definition signature type (default 'CL') + tag: the credential definition tag + support_revocation: whether to enable revocation for this credential def + + Returns: + A tuple of the credential definition ID and JSON + + """ + try: + ( + cred_def, + cred_def_private, + key_proof, + ) = await asyncio.get_event_loop().run_in_executor( + None, + lambda: CredentialDefinition.create( + origin_did, + schema, + signature_type or DEFAULT_SIGNATURE_TYPE, + tag or DEFAULT_CRED_DEF_TAG, + support_revocation=support_revocation, + ), + ) + cred_def_id = cred_def.id + cred_def_json = cred_def.to_json() + except CredxError as err: + raise IndyIssuerError("Error creating credential definition") from err + try: + async with self._profile.transaction() as txn: + await txn.handle.insert( + CATEGORY_CRED_DEF, + cred_def_id, + cred_def_json, + # Note: Indy-SDK uses a separate SchemaId record for this + tags={"schema_id": schema["id"]}, + ) + await txn.handle.insert( + CATEGORY_CRED_DEF_PRIVATE, + cred_def_id, + cred_def_private.to_json_buffer(), + ) + await txn.handle.insert( + CATEGORY_CRED_DEF_KEY_PROOF, cred_def_id, key_proof.to_json_buffer() + ) + await txn.commit() + except AskarError as err: + raise IndyIssuerError("Error storing credential definition") from err + return (cred_def_id, cred_def_json) + + async def create_credential_offer(self, credential_definition_id: str) -> str: + """ + Create a credential offer for the given credential definition id. + + Args: + credential_definition_id: The credential definition to create an offer for + + Returns: + The new credential offer + + """ + try: + async with self._profile.session() as session: + cred_def = await session.handle.fetch( + CATEGORY_CRED_DEF, credential_definition_id + ) + key_proof = await session.handle.fetch( + CATEGORY_CRED_DEF_KEY_PROOF, credential_definition_id + ) + except AskarError as err: + raise IndyIssuerError("Error retrieving credential definition") from err + if not cred_def or not key_proof: + raise IndyIssuerError( + "Credential definition not found for credential offer" + ) + try: + # The tag holds the full name of the schema, + # as opposed to just the sequence number + schema_id = cred_def.tags.get("schema_id") + cred_def = CredentialDefinition.load(cred_def.raw_value) + + credential_offer = CredentialOffer.create( + schema_id or cred_def.schema_id, + cred_def, + key_proof.raw_value, + ) + except CredxError as err: + raise IndyIssuerError("Error creating credential offer") from err + + return credential_offer.to_json() + + async def create_credential( + self, + schema: dict, + credential_offer: dict, + credential_request: dict, + credential_values: dict, + revoc_reg_id: str = None, + tails_file_path: str = None, + ) -> Tuple[str, str]: + """ + Create a credential. + + Args + schema: Schema to create credential for + credential_offer: Credential Offer to create credential for + credential_request: Credential request to create credential for + credential_values: Values to go in credential + revoc_reg_id: ID of the revocation registry + tails_file_path: The location of the tails file + + Returns: + A tuple of created credential and revocation id + + """ + credential_definition_id = credential_offer["cred_def_id"] + try: + async with self._profile.session() as session: + cred_def = await session.handle.fetch( + CATEGORY_CRED_DEF, credential_definition_id + ) + cred_def_private = await session.handle.fetch( + CATEGORY_CRED_DEF_PRIVATE, credential_definition_id + ) + except AskarError as err: + raise IndyIssuerError("Error retrieving credential definition") from err + if not cred_def or not cred_def_private: + raise IndyIssuerError( + "Credential definition not found for credential issuance" + ) + + raw_values = {} + schema_attributes = schema["attrNames"] + for attribute in schema_attributes: + # Ensure every attribute present in schema to be set. + # Extraneous attribute names are ignored. + try: + credential_value = credential_values[attribute] + except KeyError: + raise IndyIssuerError( + "Provided credential values are missing a value " + f"for the schema attribute '{attribute}'" + ) + + raw_values[attribute] = str(credential_value) + + if revoc_reg_id: + try: + async with self._profile.transaction() as txn: + rev_reg = await txn.handle.fetch(CATEGORY_REV_REG, revoc_reg_id) + rev_reg_info = await txn.handle.fetch( + CATEGORY_REV_REG_INFO, revoc_reg_id, for_update=True + ) + rev_reg_def = await txn.handle.fetch( + CATEGORY_REV_REG_DEF, revoc_reg_id + ) + rev_key = await txn.handle.fetch( + CATEGORY_REV_REG_DEF_PRIVATE, revoc_reg_id + ) + if not rev_reg: + raise IndyIssuerError("Revocation registry not found") + if not rev_reg_info: + raise IndyIssuerError("Revocation registry metadata not found") + if not rev_reg_def: + raise IndyIssuerError( + "Revocation registry definition not found" + ) + if not rev_key: + raise IndyIssuerError( + "Revocation registry definition private data not found" + ) + # NOTE: we increment the index ahead of time to keep the + # transaction short. The revocation registry itself will NOT + # be updated because we always use ISSUANCE_BY_DEFAULT. + # If something goes wrong later, the index will be skipped. + # FIXME - double check issuance type in case of upgraded wallet? + rev_info = rev_reg_info.value_json + rev_reg_index = rev_info["curr_id"] + 1 + try: + rev_reg_def = RevocationRegistryDefinition.load( + rev_reg_def.raw_value + ) + except CredxError as err: + raise IndyIssuerError( + "Error loading revocation registry definition" + ) from err + if rev_reg_index > rev_reg_def.max_cred_num: + raise IndyIssuerRevocationRegistryFullError( + "Revocation registry is full" + ) + rev_info["curr_id"] = rev_reg_index + await txn.handle.replace( + CATEGORY_REV_REG_INFO, revoc_reg_id, value_json=rev_info + ) + await txn.commit() + except AskarError as err: + raise IndyIssuerError( + "Error updating revocation registry index" + ) from err + + revoc = CredentialRevocationConfig( + rev_reg_def, + rev_key.raw_value, + rev_reg.raw_value, + rev_reg_index, + rev_info.get("used_ids") or [], + tails_file_path, + ) + credential_revocation_id = str(rev_reg_index) + else: + revoc = None + credential_revocation_id = None + + try: + ( + credential, + _upd_rev_reg, + _delta, + ) = await asyncio.get_event_loop().run_in_executor( + None, + Credential.create, + cred_def.raw_value, + cred_def_private.raw_value, + credential_offer, + credential_request, + raw_values, + None, + revoc, + ) + except CredxError as err: + raise IndyIssuerError("Error creating credential") from err + + return credential.to_json(), credential_revocation_id + + async def revoke_credentials( + self, + revoc_reg_id: str, + tails_file_path: str, + cred_revoc_ids: Sequence[str], + ) -> Tuple[str, Sequence[str]]: + """ + Revoke a set of credentials in a revocation registry. + + Args: + revoc_reg_id: ID of the revocation registry + tails_file_path: path to the local tails file + cred_revoc_ids: sequences of credential indexes in the revocation registry + + Returns: + Tuple with the combined revocation delta, list of cred rev ids not revoked + + """ + + delta = None + failed_crids = set() + max_attempt = 5 + attempt = 0 + + while True: + attempt += 1 + if attempt >= max_attempt: + raise IndyIssuerError("Repeated conflict attempting to update registry") + try: + async with self._profile.session() as session: + rev_reg_def = await session.handle.fetch( + CATEGORY_REV_REG_DEF, revoc_reg_id + ) + rev_reg = await session.handle.fetch(CATEGORY_REV_REG, revoc_reg_id) + rev_reg_info = await session.handle.fetch( + CATEGORY_REV_REG_INFO, revoc_reg_id + ) + if not rev_reg_def: + raise IndyIssuerError("Revocation registry definition not found") + if not rev_reg: + raise IndyIssuerError("Revocation registry not found") + if not rev_reg_info: + raise IndyIssuerError("Revocation registry metadata not found") + except AskarError as err: + raise IndyIssuerError("Error retrieving revocation registry") from err + + try: + rev_reg_def = RevocationRegistryDefinition.load(rev_reg_def.raw_value) + except CredxError as err: + raise IndyIssuerError( + "Error loading revocation registry definition" + ) from err + + rev_crids = set() + failed_crids = set() + max_cred_num = rev_reg_def.max_cred_num + rev_info = rev_reg_info.value_json + used_ids = set(rev_info.get("used_ids") or []) + + for rev_id in cred_revoc_ids: + rev_id = int(rev_id) + if rev_id < 1 or rev_id > max_cred_num: + LOGGER.error( + "Skipping requested credential revocation" + "on rev reg id %s, cred rev id=%s not in range", + revoc_reg_id, + rev_id, + ) + failed_crids.add(rev_id) + elif rev_id > rev_info["curr_id"]: + LOGGER.warn( + "Skipping requested credential revocation" + "on rev reg id %s, cred rev id=%s not yet issued", + revoc_reg_id, + rev_id, + ) + failed_crids.add(rev_id) + elif rev_id in used_ids: + LOGGER.warn( + "Skipping requested credential revocation" + "on rev reg id %s, cred rev id=%s already revoked", + revoc_reg_id, + rev_id, + ) + failed_crids.add(rev_id) + else: + rev_crids.add(rev_id) + + if not rev_crids: + break + + try: + rev_reg = RevocationRegistry.load(rev_reg.raw_value) + except CredxError as err: + raise IndyIssuerError("Error loading revocation registry") from err + + try: + delta = await asyncio.get_event_loop().run_in_executor( + None, + lambda: rev_reg.update( + rev_reg_def, + None, # issued + list(rev_crids), # revoked + tails_file_path, + ), + ) + except CredxError as err: + raise IndyIssuerError("Error updating revocation registry") from err + + try: + async with self._profile.transaction() as txn: + rev_reg_upd = await txn.handle.fetch( + CATEGORY_REV_REG, revoc_reg_id, for_update=True + ) + rev_info_upd = await txn.handle.fetch( + CATEGORY_REV_REG_INFO, revoc_reg_id, for_update=True + ) + if not rev_reg_upd or not rev_reg_info: + LOGGER.warn( + "Revocation registry missing, skipping update: {}", + revoc_reg_id, + ) + delta = None + break + rev_info_upd = rev_info_upd.value_json + if rev_info_upd != rev_info: + # handle concurrent update to the registry by retrying + continue + await txn.handle.replace( + CATEGORY_REV_REG, revoc_reg_id, rev_reg.to_json_buffer() + ) + used_ids.update(rev_crids) + rev_info_upd["used_ids"] = sorted(used_ids) + await txn.handle.replace( + CATEGORY_REV_REG_INFO, revoc_reg_id, value_json=rev_info_upd + ) + await txn.commit() + except AskarError as err: + raise IndyIssuerError("Error saving revocation registry") from err + break + + return ( + delta and delta.to_json(), + [str(rev_id) for rev_id in sorted(failed_crids)], + ) + + async def merge_revocation_registry_deltas( + self, fro_delta: str, to_delta: str + ) -> str: + """ + Merge revocation registry deltas. + + Args: + fro_delta: original delta in JSON format + to_delta: incoming delta in JSON format + + Returns: + Merged delta in JSON format + + """ + + def update(d1, d2): + try: + delta = RevocationRegistryDelta.load(d1) + delta.update_with(d2) + return delta.to_json() + except CredxError as err: + raise IndyIssuerError( + "Error merging revocation registry deltas" + ) from err + + return await asyncio.get_event_loop().run_in_executor( + None, update, fro_delta, to_delta + ) + + async def create_and_store_revocation_registry( + self, + origin_did: str, + cred_def_id: str, + revoc_def_type: str, + tag: str, + max_cred_num: int, + tails_base_path: str, + ) -> Tuple[str, str, str]: + """ + Create a new revocation registry and store it in the wallet. + + Args: + origin_did: the DID issuing the revocation registry + cred_def_id: the identifier of the related credential definition + revoc_def_type: the revocation registry type (default CL_ACCUM) + tag: the unique revocation registry tag + max_cred_num: the number of credentials supported in the registry + tails_base_path: where to store the tails file + issuance_type: optionally override the issuance type + + Returns: + A tuple of the revocation registry ID, JSON, and entry JSON + + """ + try: + async with self._profile.session() as session: + cred_def = await session.handle.fetch(CATEGORY_CRED_DEF, cred_def_id) + except AskarError as err: + raise IndyIssuerError("Error retrieving credential definition") from err + if not cred_def: + raise IndyIssuerError( + "Credential definition not found for revocation registry" + ) + + try: + ( + rev_reg_def, + rev_reg_def_private, + rev_reg, + _rev_reg_delta, + ) = await asyncio.get_event_loop().run_in_executor( + None, + lambda: RevocationRegistryDefinition.create( + origin_did, + cred_def.raw_value, + tag, + revoc_def_type, + max_cred_num, + tails_dir_path=tails_base_path, + ), + ) + except CredxError as err: + raise IndyIssuerError("Error creating revocation registry") from err + + rev_reg_def_id = rev_reg_def.id + rev_reg_def_json = rev_reg_def.to_json() + rev_reg_json = rev_reg.to_json() + + try: + async with self._profile.transaction() as txn: + await txn.handle.insert(CATEGORY_REV_REG, rev_reg_def_id, rev_reg_json) + await txn.handle.insert( + CATEGORY_REV_REG_INFO, + rev_reg_def_id, + value_json={"curr_id": 0, "used_ids": []}, + ) + await txn.handle.insert( + CATEGORY_REV_REG_DEF, rev_reg_def_id, rev_reg_def_json + ) + await txn.handle.insert( + CATEGORY_REV_REG_DEF_PRIVATE, + rev_reg_def_id, + rev_reg_def_private.to_json_buffer(), + ) + await txn.commit() + except AskarError as err: + raise IndyIssuerError("Error saving new revocation registry") from err + + return ( + rev_reg_def_id, + rev_reg_def_json, + rev_reg_json, + ) diff --git a/aries_cloudagent/indy/credx/tests/__init__.py b/aries_cloudagent/indy/credx/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aries_cloudagent/indy/credx/tests/test_cred_issuance.py b/aries_cloudagent/indy/credx/tests/test_cred_issuance.py new file mode 100644 index 0000000000..027c16b94d --- /dev/null +++ b/aries_cloudagent/indy/credx/tests/test_cred_issuance.py @@ -0,0 +1,341 @@ +import json +import tempfile +import pytest + +from asynctest import mock as async_mock, TestCase as AsyncTestCase + +from ....askar.profile import AskarProfileManager +from ....config.injection_context import InjectionContext +from ....ledger.base import BaseLedger +from ....ledger.multiple_ledger.ledger_requests_executor import ( + IndyLedgerRequestsExecutor, +) + +from .. import issuer, holder, verifier + + +TEST_DID = "55GkHamhTU1ZbTbV2ab9DE" +SCHEMA_NAME = "resident" +SCHEMA_VERSION = "1.0" +SCHEMA_TXN = 1234 +SCHEMA_ID = f"{TEST_DID}:2:{SCHEMA_NAME}:{SCHEMA_VERSION}" +CRED_DEF_ID = f"{TEST_DID}:3:CL:{SCHEMA_TXN}:default" +REV_REG_ID = f"{TEST_DID}:4:{CRED_DEF_ID}:CL_ACCUM:0" +CRED_REFT = "attr_0_uuid" +PRES_REQ_NON_REV = { + "name": "pres-request", + "version": "1.0", + "nonce": "1234567890", + "requested_attributes": {CRED_REFT: {"names": ["name", "moniker"]}}, + "requested_predicates": {}, +} +TIMESTAMP = 99999 +PRES_REQ_REV = { + "name": "pres-request", + "version": "1.0", + "nonce": "1234567890", + "requested_attributes": {CRED_REFT: {"names": ["name", "moniker"]}}, + "requested_predicates": {}, + "non_revoked": {"to": TIMESTAMP}, +} + + +@pytest.mark.askar +@pytest.mark.indy_credx +class TestIndyCredxIssuance(AsyncTestCase): + async def setUp(self): + context = InjectionContext(enforce_typing=False) + mock_ledger = async_mock.MagicMock( + get_credential_definition=async_mock.CoroutineMock( + return_value={"value": {}} + ), + get_revoc_reg_delta=async_mock.CoroutineMock( + return_value=( + {"value": {"...": "..."}}, + 1234567890, + ) + ), + ) + mock_ledger.__aenter__ = async_mock.CoroutineMock(return_value=mock_ledger) + self.ledger = mock_ledger + + self.holder_profile = await AskarProfileManager().provision( + context, + { + "name": ":memory:", + "key": await AskarProfileManager.generate_store_key(), + "key_derivation_method": "RAW", + }, + ) + self.issuer_profile = await AskarProfileManager().provision( + context, + { + "name": ":memory:", + "key": await AskarProfileManager.generate_store_key(), + "key_derivation_method": "RAW", + }, + ) + self.issuer_profile._context.injector.bind_instance(BaseLedger, mock_ledger) + self.issuer_profile._context.injector.bind_instance( + IndyLedgerRequestsExecutor, + async_mock.MagicMock( + get_ledger_for_identifier=async_mock.CoroutineMock( + return_value=(None, mock_ledger) + ) + ), + ) + + self.holder = holder.IndyCredxHolder(self.holder_profile) + self.issuer = issuer.IndyCredxIssuer(self.issuer_profile) + self.verifier = verifier.IndyCredxVerifier(self.issuer_profile) + assert "IndyCredxHolder" in str(self.holder) + assert "IndyCredxIssuer" in str(self.issuer) + assert "IndyCredxVerifier" in str(self.verifier) + + async def test_issue_store_non_rev(self): + assert ( + self.issuer.make_schema_id(TEST_DID, SCHEMA_NAME, SCHEMA_VERSION) + == SCHEMA_ID + ) + + (s_id, schema_json) = await self.issuer.create_schema( + TEST_DID, + SCHEMA_NAME, + SCHEMA_VERSION, + ["name", "moniker"], + ) + assert s_id == SCHEMA_ID + schema = json.loads(schema_json) + schema["seqNo"] = SCHEMA_TXN + + assert ( + self.issuer.make_credential_definition_id(TEST_DID, schema, tag="default") + == CRED_DEF_ID + ) + + ( + cd_id, + cred_def_json, + ) = await self.issuer.create_and_store_credential_definition( + TEST_DID, schema, support_revocation=False + ) + assert cd_id == CRED_DEF_ID + assert await self.issuer.credential_definition_in_wallet(cd_id) + cred_def = json.loads(cred_def_json) + + cred_offer_json = await self.issuer.create_credential_offer(cd_id) + cred_offer = json.loads(cred_offer_json) + + cred_req_json, cred_req_meta_json = await self.holder.create_credential_request( + cred_offer, cred_def, TEST_DID + ) + cred_req = json.loads(cred_req_json) + cred_req_meta = json.loads(cred_req_meta_json) + + cred_json, cred_rev_id = await self.issuer.create_credential( + schema, + cred_offer, + cred_req, + {"name": "NAME", "moniker": "MONIKER"}, + revoc_reg_id=None, + tails_file_path=None, + ) + assert cred_rev_id is None + cred_data = json.loads(cred_json) + + cred_id = await self.holder.store_credential(cred_def, cred_data, cred_req_meta) + + found = await self.holder.get_credential(cred_id) + assert found + stored_cred = json.loads(found) + + assert not await self.holder.get_mime_type(cred_id, "name") + + creds = await self.holder.get_credentials(None, None, None) + assert len(creds) == 1 + assert creds[0] == stored_cred + + assert not await self.holder.credential_revoked(self.ledger, cred_id) + + pres_creds = ( + await self.holder.get_credentials_for_presentation_request_by_referent( + PRES_REQ_NON_REV, + None, + 0, + 10, + {}, + ) + ) + assert pres_creds == [ + { + "cred_info": stored_cred, + "interval": None, + "presentation_referents": [CRED_REFT], + } + ] + + pres_json = await self.holder.create_presentation( + PRES_REQ_NON_REV, + { + "requested_attributes": { + CRED_REFT: {"cred_id": cred_id, "revealed": True} + } + }, + {s_id: schema}, + {cd_id: cred_def}, + rev_states=None, + ) + pres = json.loads(pres_json) + + assert await self.verifier.verify_presentation( + PRES_REQ_NON_REV, pres, {s_id: schema}, {cd_id: cred_def}, {}, {} + ) + + await self.holder.delete_credential(cred_id) + + async def test_issue_store_rev(self): + assert ( + self.issuer.make_schema_id(TEST_DID, SCHEMA_NAME, SCHEMA_VERSION) + == SCHEMA_ID + ) + + (s_id, schema_json) = await self.issuer.create_schema( + TEST_DID, + SCHEMA_NAME, + SCHEMA_VERSION, + ["name", "moniker"], + ) + assert s_id == SCHEMA_ID + schema = json.loads(schema_json) + schema["seqNo"] = SCHEMA_TXN + + assert ( + self.issuer.make_credential_definition_id(TEST_DID, schema, tag="default") + == CRED_DEF_ID + ) + + ( + cd_id, + cred_def_json, + ) = await self.issuer.create_and_store_credential_definition( + TEST_DID, schema, support_revocation=True + ) + assert cd_id == CRED_DEF_ID + cred_def = json.loads(cred_def_json) + self.ledger.get_credential_definition.return_value = cred_def + + with tempfile.TemporaryDirectory() as tmp_path: + ( + reg_id, + reg_def_json, + reg_entry_json, + ) = await self.issuer.create_and_store_revocation_registry( + TEST_DID, cd_id, "CL_ACCUM", "0", 10, tmp_path + ) + assert reg_id == REV_REG_ID + reg_def = json.loads(reg_def_json) + reg_entry = json.loads(reg_entry_json) + tails_path = reg_def["value"]["tailsLocation"] + + cred_offer_json = await self.issuer.create_credential_offer(cd_id) + cred_offer = json.loads(cred_offer_json) + + ( + cred_req_json, + cred_req_meta_json, + ) = await self.holder.create_credential_request( + cred_offer, cred_def, TEST_DID + ) + cred_req = json.loads(cred_req_json) + cred_req_meta = json.loads(cred_req_meta_json) + + cred_json, cred_rev_id = await self.issuer.create_credential( + schema, + cred_offer, + cred_req, + {"name": "NAME", "moniker": "MONIKER"}, + revoc_reg_id=reg_id, + tails_file_path=tails_path, + ) + assert cred_rev_id == "1" + cred_data = json.loads(cred_json) + + cred_id = await self.holder.store_credential( + cred_def, + cred_data, + cred_req_meta, + rev_reg_def=reg_def, + ) + + found = await self.holder.get_credential(cred_id) + assert found + stored_cred = json.loads(found) + + creds = await self.holder.get_credentials(None, None, None) + assert len(creds) == 1 + assert creds[0] == stored_cred + + assert not await self.holder.credential_revoked(self.ledger, cred_id) + + pres_creds = ( + await self.holder.get_credentials_for_presentation_request_by_referent( + PRES_REQ_REV, + None, + 0, + 10, + {}, + ) + ) + assert pres_creds == [ + { + "cred_info": stored_cred, + "interval": {"to": TIMESTAMP}, + "presentation_referents": [CRED_REFT], + } + ] + + rev_state_time = 1 + rev_state_json = await self.holder.create_revocation_state( + cred_rev_id, reg_def, reg_entry, rev_state_time, tails_path + ) + rev_state_init = json.loads(rev_state_json) + rev_delta_init = {"ver": "1.0", "value": rev_state_init["rev_reg"]} + + (rev_delta_2_json, skipped_ids) = await self.issuer.revoke_credentials( + reg_id, tails_path, (1,) + ) + assert not skipped_ids + rev_delta_2 = json.loads(rev_delta_2_json) + + merged = await self.issuer.merge_revocation_registry_deltas( + rev_delta_init, rev_delta_2 + ) + + pres_json = await self.holder.create_presentation( + PRES_REQ_REV, + { + "requested_attributes": { + CRED_REFT: { + "cred_id": cred_id, + "revealed": True, + "timestamp": rev_state_time, + } + } + }, + {s_id: schema}, + {cd_id: cred_def}, + rev_states={reg_id: {rev_state_time: rev_state_init}}, + ) + pres = json.loads(pres_json) + + reg_def["txnTime"] = rev_state_time + assert await self.verifier.verify_presentation( + PRES_REQ_REV, + pres, + {s_id: schema}, + {cd_id: cred_def}, + {reg_id: reg_def}, + {reg_id: {rev_state_time: rev_delta_init}}, + ) + + await self.holder.delete_credential(cred_id) diff --git a/aries_cloudagent/indy/credx/verifier.py b/aries_cloudagent/indy/credx/verifier.py new file mode 100644 index 0000000000..e625076ecd --- /dev/null +++ b/aries_cloudagent/indy/credx/verifier.py @@ -0,0 +1,85 @@ +"""Indy-Credx verifier implementation.""" + +import asyncio +import logging + +from indy_credx import CredxError, Presentation + +from ...core.profile import Profile + +from ..verifier import IndyVerifier, PresVerifyMsg + +LOGGER = logging.getLogger(__name__) + + +class IndyCredxVerifier(IndyVerifier): + """Indy-Credx verifier class.""" + + def __init__(self, profile: Profile): + """ + Initialize an IndyCredxVerifier instance. + + Args: + profile: an active profile instance + + """ + self.profile = profile + + async def verify_presentation( + self, + pres_req, + pres, + schemas, + credential_definitions, + rev_reg_defs, + rev_reg_entries, + ) -> (bool, list): + """ + Verify a presentation. + + Args: + pres_req: Presentation request data + pres: Presentation data + schemas: Schema data + credential_definitions: credential definition data + rev_reg_defs: revocation registry definitions + rev_reg_entries: revocation registry entries + """ + + msgs = [] + try: + msgs += self.non_revoc_intervals(pres_req, pres, credential_definitions) + msgs += await self.check_timestamps( + self.profile, pres_req, pres, rev_reg_defs + ) + msgs += await self.pre_verify(pres_req, pres) + except ValueError as err: + s = str(err) + msgs.append(f"{PresVerifyMsg.PRES_VALUE_ERROR.value}::{s}") + LOGGER.error( + f"Presentation on nonce={pres_req['nonce']} " + f"cannot be validated: {str(err)}" + ) + return (False, msgs) + + try: + presentation = Presentation.load(pres) + verified = await asyncio.get_event_loop().run_in_executor( + None, + presentation.verify, + pres_req, + schemas.values(), + credential_definitions.values(), + rev_reg_defs.values(), + rev_reg_entries, + ) + except CredxError as err: + s = str(err) + msgs.append(f"{PresVerifyMsg.PRES_VERIFY_ERROR.value}::{s}") + LOGGER.exception( + f"Validation of presentation on nonce={pres_req['nonce']} " + "failed with error" + ) + verified = False + + return (verified, msgs) diff --git a/aries_cloudagent/indy/holder.py b/aries_cloudagent/indy/holder.py new file mode 100644 index 0000000000..5cd9a2a249 --- /dev/null +++ b/aries_cloudagent/indy/holder.py @@ -0,0 +1,164 @@ +"""Base Indy Holder class.""" + +from abc import ABC, ABCMeta, abstractmethod +from typing import Tuple, Union + +from ..core.error import BaseError +from ..ledger.base import BaseLedger + + +class IndyHolderError(BaseError): + """Base class for holder exceptions.""" + + +class IndyHolder(ABC, metaclass=ABCMeta): + """Base class for holder.""" + + RECORD_TYPE_MIME_TYPES = "attribute-mime-types" + CHUNK = 256 + + def __repr__(self) -> str: + """ + Return a human readable representation of this class. + + Returns: + A human readable string for this class + + """ + return "<{}>".format(self.__class__.__name__) + + @abstractmethod + async def get_credential(self, credential_id: str) -> str: + """ + Get a credential stored in the wallet. + + Args: + credential_id: Credential id to retrieve + + """ + + @abstractmethod + async def credential_revoked( + self, ledger: BaseLedger, credential_id: str, fro: int = None, to: int = None + ) -> bool: + """ + Check ledger for revocation status of credential by cred id. + + Args: + credential_id: Credential id to check + + """ + + @abstractmethod + async def delete_credential(self, credential_id: str): + """ + Remove a credential stored in the wallet. + + Args: + credential_id: Credential id to remove + + """ + + @abstractmethod + async def get_mime_type( + self, credential_id: str, attr: str = None + ) -> Union[dict, str]: + """ + Get MIME type per attribute (or for all attributes). + + Args: + credential_id: credential id + attr: attribute of interest or omit for all + + Returns: Attribute MIME type or dict mapping attribute names to MIME types + attr_meta_json = all_meta.tags.get(attr) + + """ + + @abstractmethod + async def create_presentation( + self, + presentation_request: dict, + requested_credentials: dict, + schemas: dict, + credential_definitions: dict, + rev_states: dict = None, + ) -> str: + """ + Get credentials stored in the wallet. + + Args: + presentation_request: Valid indy format presentation request + requested_credentials: Indy format requested credentials + schemas: Indy formatted schemas JSON + credential_definitions: Indy formatted credential definitions JSON + rev_states: Indy format revocation states JSON + """ + + @abstractmethod + async def create_credential_request( + self, credential_offer: dict, credential_definition: dict, holder_did: str + ) -> Tuple[str, str]: + """ + Create a credential request for the given credential offer. + + Args: + credential_offer: The credential offer to create request for + credential_definition: The credential definition to create an offer for + holder_did: the DID of the agent making the request + + Returns: + A tuple of the credential request and credential request metadata + + """ + + @abstractmethod + async def store_credential( + self, + credential_definition: dict, + credential_data: dict, + credential_request_metadata: dict, + credential_attr_mime_types=None, + credential_id: str = None, + rev_reg_def: dict = None, + ): + """ + Store a credential in the wallet. + + Args: + credential_definition: Credential definition for this credential + credential_data: Credential data generated by the issuer + credential_request_metadata: credential request metadata generated + by the issuer + credential_attr_mime_types: dict mapping attribute names to (optional) + MIME types to store as non-secret record, if specified + credential_id: optionally override the stored credential id + rev_reg_def: revocation registry definition in json + + Returns: + the ID of the stored credential + + """ + + @abstractmethod + async def create_revocation_state( + self, + cred_rev_id: str, + rev_reg_def: dict, + rev_reg_delta: dict, + timestamp: int, + tails_file_path: str, + ) -> str: + """ + Create current revocation state for a received credential. + + Args: + cred_rev_id: credential revocation id in revocation registry + rev_reg_def: revocation registry definition + rev_reg_delta: revocation delta + timestamp: delta timestamp + + Returns: + the revocation state + + """ diff --git a/aries_cloudagent/indy/issuer.py b/aries_cloudagent/indy/issuer.py new file mode 100644 index 0000000000..d889746d41 --- /dev/null +++ b/aries_cloudagent/indy/issuer.py @@ -0,0 +1,204 @@ +"""Base Indy Issuer class.""" + +from abc import ABC, ABCMeta, abstractmethod +from typing import Sequence, Tuple + +from ..core.error import BaseError + + +DEFAULT_CRED_DEF_TAG = "default" +DEFAULT_SIGNATURE_TYPE = "CL" + + +class IndyIssuerError(BaseError): + """Generic issuer error.""" + + +class IndyIssuerRevocationRegistryFullError(IndyIssuerError): + """Revocation registry is full when issuing a new credential.""" + + +class IndyIssuer(ABC, metaclass=ABCMeta): + """Base class for Indy Issuer.""" + + def __repr__(self) -> str: + """ + Return a human readable representation of this class. + + Returns: + A human readable string for this class + + """ + return "<{}>".format(self.__class__.__name__) + + def make_schema_id( + self, origin_did: str, schema_name: str, schema_version: str + ) -> str: + """Derive the ID for a schema.""" + return f"{origin_did}:2:{schema_name}:{schema_version}" + + @abstractmethod + async def create_schema( + self, + origin_did: str, + schema_name: str, + schema_version: str, + attribute_names: Sequence[str], + ) -> Tuple[str, str]: + """ + Create a new credential schema and store it in the wallet. + + Args: + origin_did: the DID issuing the credential definition + schema_name: the schema name + schema_version: the schema version + attribute_names: a sequence of schema attribute names + + Returns: + A tuple of the schema ID and JSON + + """ + + def make_credential_definition_id( + self, origin_did: str, schema: dict, signature_type: str = None, tag: str = None + ) -> str: + """Derive the ID for a credential definition.""" + signature_type = signature_type or DEFAULT_SIGNATURE_TYPE + tag = tag or DEFAULT_CRED_DEF_TAG + return f"{origin_did}:3:{signature_type}:{str(schema['seqNo'])}:{tag}" + + @abstractmethod + async def credential_definition_in_wallet( + self, credential_definition_id: str + ) -> bool: + """ + Check whether a given credential definition ID is present in the wallet. + + Args: + credential_definition_id: The credential definition ID to check + """ + + @abstractmethod + async def create_and_store_credential_definition( + self, + origin_did: str, + schema: dict, + signature_type: str = None, + tag: str = None, + support_revocation: bool = False, + ) -> Tuple[str, str]: + """ + Create a new credential definition and store it in the wallet. + + Args: + origin_did: the DID issuing the credential definition + schema_json: the schema used as a basis + signature_type: the credential definition signature type (default 'CL') + tag: the credential definition tag + support_revocation: whether to enable revocation for this credential def + + Returns: + A tuple of the credential definition ID and JSON + + """ + + @abstractmethod + async def create_credential_offer(self, credential_definition_id) -> str: + """ + Create a credential offer for the given credential definition id. + + Args: + credential_definition_id: The credential definition to create an offer for + + Returns: + The created credential offer + + """ + + @abstractmethod + async def create_credential( + self, + schema: dict, + credential_offer: dict, + credential_request: dict, + credential_values: dict, + revoc_reg_id: str = None, + tails_file_path: str = None, + ) -> Tuple[str, str]: + """ + Create a credential. + + Args + schema: Schema to create credential for + credential_offer: Credential Offer to create credential for + credential_request: Credential request to create credential for + credential_values: Values to go in credential + revoc_reg_id: ID of the revocation registry + tails_file_path: The location of the tails file + + Returns: + A tuple of created credential and revocation id + + """ + + @abstractmethod + async def revoke_credentials( + self, + revoc_reg_id: str, + tails_file_path: str, + cred_rev_ids: Sequence[str], + ) -> Tuple[str, Sequence[str]]: + """ + Revoke a set of credentials in a revocation registry. + + Args: + revoc_reg_id: ID of the revocation registry + tails_file_path: path to the local tails file + cred_rev_ids: sequences of credential indexes in the revocation registry + + Returns: + Tuple with the combined revocation delta, list of cred rev ids not revoked + + """ + + @abstractmethod + async def create_and_store_revocation_registry( + self, + origin_did: str, + cred_def_id: str, + revoc_def_type: str, + tag: str, + max_cred_num: int, + tails_base_path: str, + ) -> Tuple[str, str, str]: + """ + Create a new revocation registry and store it in the wallet. + + Args: + origin_did: the DID issuing the revocation registry + cred_def_id: the identifier of the related credential definition + revoc_def_type: the revocation registry type (default CL_ACCUM) + tag: the unique revocation registry tag + max_cred_num: the number of credentials supported in the registry + tails_base_path: where to store the tails file + + Returns: + A tuple of the revocation registry ID, JSON, and entry JSON + + """ + + @abstractmethod + async def merge_revocation_registry_deltas( + self, fro_delta: str, to_delta: str + ) -> str: + """ + Merge revocation registry deltas. + + Args: + fro_delta: original delta in JSON format + to_delta: incoming delta in JSON format + + Returns: + Merged delta in JSON format + + """ diff --git a/aries_cloudagent/indy/models/__init__.py b/aries_cloudagent/indy/models/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aries_cloudagent/indy/models/cred.py b/aries_cloudagent/indy/models/cred.py new file mode 100644 index 0000000000..0003212fe0 --- /dev/null +++ b/aries_cloudagent/indy/models/cred.py @@ -0,0 +1,129 @@ +"""Credential artifacts.""" + +from typing import Mapping + +from marshmallow import EXCLUDE, fields + +from ...messaging.models.base import BaseModel, BaseModelSchema +from ...messaging.valid import ( + INDY_CRED_DEF_ID, + INDY_REV_REG_ID, + INDY_SCHEMA_ID, + NUM_STR_ANY, +) + + +class IndyAttrValue(BaseModel): + """Indy attribute value.""" + + class Meta: + """Indy attribute value.""" + + schema_class = "IndyAttrValueSchema" + + def __init__(self, raw: str = None, encoded: str = None, **kwargs): + """Initialize indy (credential) attribute value.""" + super().__init__(**kwargs) + self.raw = raw + self.encoded = encoded + + +class IndyAttrValueSchema(BaseModelSchema): + """Indy attribute value schema.""" + + class Meta: + """Indy attribute value schema metadata.""" + + model_class = IndyAttrValue + unknown = EXCLUDE + + raw = fields.Str( + required=True, + description="Attribute raw value", + ) + encoded = fields.Str( + required=True, + description="Attribute encoded value", + **NUM_STR_ANY, + ) + + +class IndyCredential(BaseModel): + """Indy credential.""" + + class Meta: + """Indy credential metadata.""" + + schema_class = "IndyCredentialSchema" + + def __init__( + self, + schema_id: str = None, + cred_def_id: str = None, + rev_reg_id: str = None, + values: Mapping[str, IndyAttrValue] = None, + signature: Mapping = None, + signature_correctness_proof: Mapping = None, + rev_reg: Mapping = None, + witness: Mapping = None, + ): + """Initialize indy 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 IndyCredentialSchema(BaseModelSchema): + """Indy credential schema.""" + + class Meta: + """Indy credential schemametadata.""" + + model_class = IndyCredential + unknown = EXCLUDE + + schema_id = fields.Str( + required=True, + description="Schema identifier", + **INDY_SCHEMA_ID, + ) + cred_def_id = fields.Str( + required=True, + description="Credential definition identifier", + **INDY_CRED_DEF_ID, + ) + rev_reg_id = fields.Str( + allow_none=True, + description="Revocation registry identifier", + **INDY_REV_REG_ID, + ) + values = fields.Dict( + keys=fields.Str(description="Attribute name"), + values=fields.Nested( + IndyAttrValueSchema(), + description="Attribute value", + ), + required=True, + description="Credential attributes", + ) + signature = fields.Dict( + required=True, + description="Credential signature", + ) + signature_correctness_proof = fields.Dict( + required=True, + description="Credential signature correctness proof", + ) + rev_reg = fields.Dict( + allow_none=True, + description="Revocation registry state", + ) + witness = fields.Dict( + allow_none=True, + description="Witness for revocation proof", + ) diff --git a/aries_cloudagent/indy/models/cred_abstract.py b/aries_cloudagent/indy/models/cred_abstract.py new file mode 100644 index 0000000000..a11c687153 --- /dev/null +++ b/aries_cloudagent/indy/models/cred_abstract.py @@ -0,0 +1,130 @@ +"""Cred abstract artifacts to attach to RFC 453 messages.""" + +from typing import Sequence + +from marshmallow import EXCLUDE, fields + +from ...messaging.models.base import BaseModel, BaseModelSchema +from ...messaging.valid import INDY_CRED_DEF_ID, INDY_SCHEMA_ID, NUM_STR_WHOLE + + +class IndyKeyCorrectnessProof(BaseModel): + """Indy key correctness proof.""" + + class Meta: + """IndyKeyCorrectnessProof metadata.""" + + schema_class = "IndyKeyCorrectnessProofSchema" + + def __init__( + self, + c: str = None, + xz_cap: str = None, + xr_cap: Sequence[Sequence[str]] = None, + **kwargs, + ): + """Initialize XR cap for indy key correctness proof.""" + super().__init__(**kwargs) + + self.c = c + self.xz_cap = xz_cap + self.xr_cap = xr_cap + + +class IndyKeyCorrectnessProofSchema(BaseModelSchema): + """Indy key correctness proof schema.""" + + class Meta: + """Indy key correctness proof schema metadata.""" + + model_class = IndyKeyCorrectnessProof + unknown = EXCLUDE + + c = fields.Str( + required=True, + description="c in key correctness proof", + **NUM_STR_WHOLE, + ) + xz_cap = fields.Str( + required=True, + description="xz_cap in key correctness proof", + **NUM_STR_WHOLE, + ) + xr_cap = fields.List( + fields.List( + fields.Str( + required=True, + description="xr_cap component values in key correctness proof", + ), + required=True, + description="xr_cap components in key correctness proof", + many=True, + ), + required=True, + description="xr_cap in key correctness proof", + many=True, + ) + + +class IndyCredAbstract(BaseModel): + """Indy credential abstract.""" + + class Meta: + """Indy credential abstract metadata.""" + + schema_class = "IndyCredAbstractSchema" + + def __init__( + self, + schema_id: str = None, + cred_def_id: str = None, + nonce: str = None, + key_correctness_proof: str = None, + **kwargs, + ): + """ + Initialize indy cred abstract object. + + Args: + schema_id: schema identifier + cred_def_id: credential definition identifier + nonce: nonce + key_correctness_proof: key correctness proof + + """ + 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 IndyCredAbstractSchema(BaseModelSchema): + """Indy credential abstract schema.""" + + class Meta: + """Indy credential abstract schema metadata.""" + + model_class = IndyCredAbstract + unknown = EXCLUDE + + schema_id = fields.Str( + required=True, + description="Schema identifier", + **INDY_SCHEMA_ID, + ) + cred_def_id = fields.Str( + required=True, + description="Credential definition identifier", + **INDY_CRED_DEF_ID, + ) + nonce = fields.Str( + required=True, + description="Nonce in credential abstract", + **NUM_STR_WHOLE, + ) + key_correctness_proof = fields.Nested( + IndyKeyCorrectnessProofSchema(), + required=True, + description="Key correctness proof", + ) diff --git a/aries_cloudagent/indy/models/cred_def.py b/aries_cloudagent/indy/models/cred_def.py new file mode 100644 index 0000000000..2124041c06 --- /dev/null +++ b/aries_cloudagent/indy/models/cred_def.py @@ -0,0 +1,83 @@ +"""Schema artifacts.""" + +from marshmallow import fields, Schema + +from ...messaging.models.openapi import OpenAPISchema +from ...messaging.valid import INDY_CRED_DEF_ID, INDY_VERSION, NUM_STR_WHOLE + + +class CredDefValuePrimarySchema(OpenAPISchema): + """Cred def value primary schema.""" + + n = fields.Str(**NUM_STR_WHOLE) + s = fields.Str(**NUM_STR_WHOLE) + r = fields.Nested( + Schema.from_dict( + { + "master_secret": fields.Str(**NUM_STR_WHOLE), + "number": fields.Str(**NUM_STR_WHOLE), + "remainder": fields.Str(**NUM_STR_WHOLE), + } + ), + name="CredDefValuePrimaryRSchema", + ) + rctxt = fields.Str(**NUM_STR_WHOLE) + z = fields.Str(**NUM_STR_WHOLE) + + +class CredDefValueRevocationSchema(OpenAPISchema): + """Cred def value revocation schema.""" + + g = fields.Str(example="1 1F14F&ECB578F 2 095E45DDF417D") + g_dash = fields.Str(example="1 1D64716fCDC00C 1 0C781960FA66E3D3 2 095E45DDF417D") + h = fields.Str(example="1 16675DAE54BFAE8 2 095E45DD417D") + h0 = fields.Str(example="1 21E5EF9476EAF18 2 095E45DDF417D") + h1 = fields.Str(example="1 236D1D99236090 2 095E45DDF417D") + h2 = fields.Str(example="1 1C3AE8D1F1E277 2 095E45DDF417D") + htilde = fields.Str(example="1 1D8549E8C0F8 2 095E45DDF417D") + h_cap = fields.Str(example="1 1B2A32CF3167 1 2490FEBF6EE55 1 0000000000000000") + u = fields.Str(example="1 0C430AAB2B4710 1 1CB3A0932EE7E 1 0000000000000000") + pk = fields.Str(example="1 142CD5E5A7DC 1 153885BD903312 2 095E45DDF417D") + y = fields.Str(example="1 153558BD903312 2 095E45DDF417D 1 0000000000000000") + + +class CredDefValueSchema(OpenAPISchema): + """Cred def value schema.""" + + primary = fields.Nested( + CredDefValuePrimarySchema(), + description="Primary value for credential definition", + ) + revocation = fields.Nested( + CredDefValueRevocationSchema(), + description="Revocation value for credential definition", + ) + + +class CredentialDefinitionSchema(OpenAPISchema): + """Marshmallow schema for indy cred def.""" + + ver = fields.Str(description="Node protocol version", **INDY_VERSION) + ident = fields.Str( + description="Credential definition identifier", + data_key="id", + **INDY_CRED_DEF_ID, + ) + schemaId = fields.Str( + description="Schema identifier within credential definition identifier", + example=":".join(INDY_CRED_DEF_ID["example"].split(":")[3:-1]), # long or short + ) + typ = fields.Constant( + constant="CL", + description="Signature type: CL for Camenisch-Lysyanskaya", + data_key="type", + example="CL", + ) + tag = fields.Str( + description="Tag within credential definition identifier", + example=INDY_CRED_DEF_ID["example"].split(":")[-1], + ) + value = fields.Nested( + CredDefValueSchema(), + description="Credential definition primary and revocation values", + ) diff --git a/aries_cloudagent/indy/models/cred_precis.py b/aries_cloudagent/indy/models/cred_precis.py new file mode 100644 index 0000000000..0a1285fd50 --- /dev/null +++ b/aries_cloudagent/indy/models/cred_precis.py @@ -0,0 +1,100 @@ +"""Admin routes for presentations.""" + +from typing import Mapping + +from marshmallow import EXCLUDE, fields + +from ...messaging.models.base import BaseModel, BaseModelSchema +from ...messaging.models.openapi import OpenAPISchema +from ...messaging.valid import ( + INDY_CRED_DEF_ID, + INDY_CRED_REV_ID, + INDY_REV_REG_ID, + INDY_SCHEMA_ID, + UUIDFour, +) + +from .non_rev_interval import IndyNonRevocationIntervalSchema + + +class IndyCredInfo(BaseModel): + """Indy cred info, as holder gets via indy-sdk.""" + + class Meta: + """IndyCredInfo metadata.""" + + schema_class = "IndyCredInfoSchema" + + def __init__( + self, + referent: str = None, + attrs: Mapping = None, + schema_id: str = None, + cred_def_id: str = None, + rev_reg_id: str = None, + cred_rev_id: str = None, + ): + """Initialize indy cred info.""" + self.referent = referent + self.attrs = attrs + self.schema_id = schema_id + self.cred_def_id = cred_def_id + self.rev_reg_id = rev_reg_id + self.cred_rev_id = cred_rev_id + + +class IndyCredInfoSchema(BaseModelSchema): + """Schema for indy cred-info.""" + + class Meta: + """Schema metadata.""" + + model_class = IndyCredInfo + unknown = EXCLUDE + + referent = fields.Str( + description="Wallet referent", + example=UUIDFour.EXAMPLE, # typically but not necessarily a UUID4 + ) + attrs = fields.Dict( + description="Attribute names and value", + keys=fields.Str(example="userid"), # marshmallow/apispec v3.0 ignores + values=fields.Str(example="alice"), + ) + schema_id = fields.Str( + description="Schema identifier", + **INDY_SCHEMA_ID, + ) + cred_def_id = fields.Str( + description="Credential definition identifier", + **INDY_CRED_DEF_ID, + ) + rev_reg_id = fields.Str( + description="Revocation registry identifier", + **INDY_REV_REG_ID, + allow_none=True, + ) + cred_rev_id = fields.Str( + description="Credential revocation identifier", + **INDY_CRED_REV_ID, + allow_none=True, + ) + + +class IndyCredPrecisSchema(OpenAPISchema): + """Schema for precis that indy credential search returns (and aca-py augments).""" + + cred_info = fields.Nested( + IndyCredInfoSchema(), + description="Credential info", + ) + interval = fields.Nested( + IndyNonRevocationIntervalSchema(), + description="Non-revocation interval from presentation request", + ) + presentation_referents = fields.List( # aca-py augments with pres_referents + fields.Str( + description="presentation referent", + example="1_age_uuid", + ), + ) diff --git a/aries_cloudagent/indy/models/cred_request.py b/aries_cloudagent/indy/models/cred_request.py new file mode 100644 index 0000000000..92a80e14d6 --- /dev/null +++ b/aries_cloudagent/indy/models/cred_request.py @@ -0,0 +1,68 @@ +"""Cred request artifacts to attach to RFC 453 messages.""" + +from typing import Mapping + +from marshmallow import EXCLUDE, fields + +from ...messaging.models.base import BaseModel, BaseModelSchema +from ...messaging.valid import INDY_CRED_DEF_ID, INDY_DID, NUM_STR_WHOLE + + +class IndyCredRequest(BaseModel): + """Indy credential request.""" + + class Meta: + """Indy credential request metadata.""" + + schema_class = "IndyCredRequestSchema" + + def __init__( + self, + prover_did: str = None, + cred_def_id: str = None, + blinded_ms: Mapping = None, + blinded_ms_correctness_proof: Mapping = None, + nonce: str = None, + **kwargs, + ): + """Initialize indy 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 IndyCredRequestSchema(BaseModelSchema): + """Indy credential request schema.""" + + class Meta: + """Indy credential request schema metadata.""" + + model_class = IndyCredRequest + unknown = EXCLUDE + + prover_did = fields.Str( + required=True, + description="Prover DID", + **INDY_DID, + ) + cred_def_id = fields.Str( + required=True, + description="Credential definition identifier", + **INDY_CRED_DEF_ID, + ) + blinded_ms = fields.Dict( + required=True, + description="Blinded master secret", + ) + blinded_ms_correctness_proof = fields.Dict( + required=True, + description="Blinded master secret correctness proof", + ) + nonce = fields.Str( + required=True, + description="Nonce in credential request", + **NUM_STR_WHOLE, + ) diff --git a/aries_cloudagent/indy/models/non_rev_interval.py b/aries_cloudagent/indy/models/non_rev_interval.py new file mode 100644 index 0000000000..65e2eb8e39 --- /dev/null +++ b/aries_cloudagent/indy/models/non_rev_interval.py @@ -0,0 +1,70 @@ +"""Indy non-revocation interval.""" + +from time import time + +from marshmallow import EXCLUDE, fields + +from ...messaging.models.base import BaseModel, BaseModelSchema +from ...messaging.valid import INT_EPOCH + + +class IndyNonRevocationInterval(BaseModel): + """Indy non-revocation interval.""" + + class Meta: + """NonRevocationInterval metadata.""" + + schema_class = "IndyNonRevocationIntervalSchema" + + def __init__(self, fro: int = None, to: int = None, **kwargs): + """Initialize non-revocation interval. + + Args: + fro: earliest time of interest + to: latest time of interest + + """ + super().__init__(**kwargs) + self.fro = fro + self.to = to + + def covers(self, timestamp: 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 IndyNonRevocationIntervalSchema(BaseModelSchema): + """Schema to allow serialization/deserialization of non-revocation intervals.""" + + class Meta: + """IndyNonRevocationIntervalSchema metadata.""" + + model_class = IndyNonRevocationInterval + unknown = EXCLUDE + + fro = fields.Int( + required=False, + description="Earliest time of interest in non-revocation interval", + data_key="from", + strict=True, + **INT_EPOCH + ) + to = fields.Int( + required=False, + description="Latest time of interest in non-revocation interval", + strict=True, + **INT_EPOCH + ) diff --git a/aries_cloudagent/indy/models/predicate.py b/aries_cloudagent/indy/models/predicate.py new file mode 100644 index 0000000000..ac80e037fa --- /dev/null +++ b/aries_cloudagent/indy/models/predicate.py @@ -0,0 +1,84 @@ +"""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 indy-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 indy 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/aries_cloudagent/indy/models/pres_preview.py b/aries_cloudagent/indy/models/pres_preview.py new file mode 100644 index 0000000000..0edb1cdb06 --- /dev/null +++ b/aries_cloudagent/indy/models/pres_preview.py @@ -0,0 +1,477 @@ +"""A presentation preview inner object.""" + +from enum import Enum +from time import time +from typing import Mapping, Sequence + +from marshmallow import EXCLUDE, fields + +from ...core.profile import Profile +from ...ledger.multiple_ledger.ledger_requests_executor import ( + GET_CRED_DEF, + IndyLedgerRequestsExecutor, +) +from ...messaging.models.base import BaseModel, BaseModelSchema +from ...messaging.util import canon +from ...messaging.valid import INDY_CRED_DEF_ID, INDY_PREDICATE +from ...multitenant.base import BaseMultitenantManager +from ...protocols.didcomm_prefix import DIDCommPrefix +from ...wallet.util import b64_to_str + +from ..util import generate_pr_nonce + +from .non_rev_interval import IndyNonRevocationInterval +from .predicate import Predicate + +PRESENTATION_PREVIEW = "present-proof/1.0/presentation-preview" # message type + + +class IndyPresPredSpec(BaseModel): + """Class representing a predicate specification within a presentation preview.""" + + class Meta: + """Pred spec metadata.""" + + schema_class = "IndyPresPredSpecSchema" + + def __init__( + self, + name: str, + *, + cred_def_id: str = None, + predicate: str, + threshold: int, + **kwargs, + ): + """ + Initialize preview object. + + Args: + name: attribute name + cred_def_id: credential definition identifier + predicate: predicate type (e.g., ">=") + threshold: threshold value + + """ + super().__init__(**kwargs) + self.name = name + self.cred_def_id = cred_def_id + self.predicate = predicate + self.threshold = threshold + + def __eq__(self, other): + """Equality comparator.""" + + if canon(self.name) != canon(other.name): + return False # distinct attribute names modulo canonicalization + + if self.cred_def_id != other.cred_def_id: + return False + + if self.predicate != other.predicate: + return False + + return self.threshold == other.threshold + + +class IndyPresPredSpecSchema(BaseModelSchema): + """Predicate specifiation schema.""" + + class Meta: + """Predicate specifiation schema metadata.""" + + model_class = IndyPresPredSpec + unknown = EXCLUDE + + name = fields.Str(description="Attribute name", required=True, example="high_score") + cred_def_id = fields.Str( + description="Credential definition identifier", + required=False, + **INDY_CRED_DEF_ID, + ) + predicate = fields.Str( + description="Predicate type ('<', '<=', '>=', or '>')", + required=True, + **INDY_PREDICATE, + ) + threshold = fields.Int(description="Threshold value", required=True, strict=True) + + +class IndyPresAttrSpec(BaseModel): + """Class representing an attibute specification within a presentation preview.""" + + class Meta: + """Attr spec metadata.""" + + schema_class = "IndyPresAttrSpecSchema" + + class Posture(Enum): + """Attribute posture: self-attested, revealed claim or unrevealed claim.""" + + SELF_ATTESTED = 0 + REVEALED_CLAIM = 1 + UNREVEALED_CLAIM = 2 + + def __init__( + self, + name: str, + cred_def_id: str = None, + mime_type: str = None, + value: str = None, + referent: str = None, + **kwargs, + ): + """ + Initialize attribute specification object. + + Args: + name: attribute name + cred_def_id: credential definition identifier + (None for self-attested attribute) + mime_type: MIME type + value: attribute value as credential stores it + (None for unrevealed attribute) + referent: credential referent + + """ + super().__init__(**kwargs) + self.name = name + self.cred_def_id = cred_def_id + self.mime_type = mime_type.lower() if mime_type else None + self.value = value + self.referent = referent + + @staticmethod + def list_plain(plain: dict, cred_def_id: str, referent: str = None): + """ + Return a list of `IndyPresAttrSpec` on input cred def id. + + Args: + plain: dict mapping names to values + cred_def_id: credential definition identifier to specify + referent: single referent to use, omit for none + + Returns: + List of IndyPresAttrSpec on input cred def id with no MIME types + + """ + return [ + IndyPresAttrSpec( + name=k, cred_def_id=cred_def_id, value=plain[k], referent=referent + ) + for k in plain + ] + + @property + def posture(self) -> "IndyPresAttrSpec.Posture": + """Attribute posture: self-attested, revealed claim, or unrevealed claim.""" + + if self.cred_def_id: + if self.value: + return IndyPresAttrSpec.Posture.REVEALED_CLAIM + return IndyPresAttrSpec.Posture.UNREVEALED_CLAIM + if self.value: + return IndyPresAttrSpec.Posture.SELF_ATTESTED + + return None + + def b64_decoded_value(self) -> str: + """Value, base64-decoded if applicable.""" + + return b64_to_str(self.value) if self.value and self.mime_type else self.value + + def satisfies(self, pred_spec: IndyPresPredSpec): + """Whether current specified attribute satisfies input specified predicate.""" + + return bool( + self.value + and not self.mime_type + and canon(self.name) == canon(pred_spec.name) + and not pred_spec.cred_def_id + or (self.cred_def_id == pred_spec.cred_def_id) + and Predicate.get(pred_spec.predicate).value.yes( + self.value, pred_spec.threshold + ) + ) + + def __eq__(self, other): + """Equality comparator.""" + + if canon(self.name) != canon(other.name): + return False # distinct attribute names + + if self.cred_def_id != other.cred_def_id: + return False # distinct attribute cred def ids + + if self.mime_type != other.mime_type: + return False # distinct MIME types + + if self.referent != other.referent: + return False # distinct credential referents + + return self.b64_decoded_value() == other.b64_decoded_value() + + +class IndyPresAttrSpecSchema(BaseModelSchema): + """Attribute specifiation schema.""" + + class Meta: + """Attribute specifiation schema metadata.""" + + model_class = IndyPresAttrSpec + unknown = EXCLUDE + + name = fields.Str( + description="Attribute name", required=True, example="favourite_drink" + ) + cred_def_id = fields.Str(required=False, **INDY_CRED_DEF_ID) + mime_type = fields.Str( + description="MIME type (default null)", + required=False, + data_key="mime-type", + example="image/jpeg", + ) + value = fields.Str(description="Attribute value", required=False, example="martini") + referent = fields.Str( + description="Credential referent", required=False, example="0" + ) + + +class IndyPresPreview(BaseModel): + """Class representing presentation preview.""" + + class Meta: + """Presentation preview metadata.""" + + schema_class = "IndyPresPreviewSchema" + message_type = PRESENTATION_PREVIEW + + def __init__( + self, + *, + _type: str = None, + attributes: Sequence[IndyPresAttrSpec] = None, + predicates: Sequence[IndyPresPredSpec] = None, + **kwargs, + ): + """ + Initialize presentation preview object. + + Args: + _type: formalism for Marshmallow model creation: ignored + attributes: list of attribute specifications + predicates: list of predicate specifications + + """ + super().__init__(**kwargs) + self.attributes = list(attributes) if attributes else [] + self.predicates = list(predicates) if predicates else [] + + @property + def _type(self): + """Accessor for message type.""" + + return DIDCommPrefix.qualify_current(IndyPresPreview.Meta.message_type) + + def has_attr_spec(self, cred_def_id: str, name: str, value: str) -> bool: + """ + Return whether preview contains given attribute specification. + + Args: + cred_def_id: credential definition identifier + name: attribute name + value: attribute value + + Returns: + Whether preview contains matching attribute specification. + + """ + + return any( + canon(a.name) == canon(name) + and a.value in (value, None) + and a.cred_def_id == cred_def_id + for a in self.attributes + ) + + async def indy_proof_request( + self, + profile: Profile = None, + name: str = None, + version: str = None, + nonce: str = None, + non_revoc_intervals: Mapping[str, IndyNonRevocationInterval] = None, + ) -> dict: + """ + Return indy proof request corresponding to presentation preview. + + Typically the verifier turns the proof preview into a proof request. + + Args: + name: for proof request + version: version for proof request + nonce: nonce for proof request + ledger: ledger with credential definitions, to check for revocation support + non_revoc_intervals: non-revocation interval to use per cred def id + where applicable (default from and to the current time if applicable) + + Returns: + Indy proof request dict. + + """ + + def non_revoc(cred_def_id: str) -> IndyNonRevocationInterval: + """Non-revocation interval to use for input cred def id.""" + + nonlocal epoch_now + nonlocal non_revoc_intervals + + return (non_revoc_intervals or {}).get( + cred_def_id, IndyNonRevocationInterval(epoch_now, epoch_now) + ) + + epoch_now = int(time()) + ledger = None + proof_req = { + "name": name or "proof-request", + "version": version or "1.0", + "nonce": nonce or await generate_pr_nonce(), + "requested_attributes": {}, + "requested_predicates": {}, + } + + attr_specs_names = {} + for attr_spec in self.attributes: + if attr_spec.posture == IndyPresAttrSpec.Posture.SELF_ATTESTED: + proof_req["requested_attributes"][ + "self_{}_uuid".format(canon(attr_spec.name)) + ] = {"name": attr_spec.name} + continue + + cd_id = attr_spec.cred_def_id + revoc_support = False + if cd_id: + if profile: + multitenant_mgr = profile.inject_or(BaseMultitenantManager) + if multitenant_mgr: + ledger_exec_inst = IndyLedgerRequestsExecutor(profile) + else: + ledger_exec_inst = profile.inject(IndyLedgerRequestsExecutor) + ledger = ( + await ledger_exec_inst.get_ledger_for_identifier( + cd_id, + txn_record_type=GET_CRED_DEF, + ) + )[1] + if ledger: + async with ledger: + revoc_support = (await ledger.get_credential_definition(cd_id))[ + "value" + ].get("revocation") + interval = non_revoc(cd_id) if revoc_support else None + + if attr_spec.referent: + if attr_spec.referent in attr_specs_names: + attr_specs_names[attr_spec.referent]["names"].append(attr_spec.name) + else: + attr_specs_names[attr_spec.referent] = { + "names": [attr_spec.name], + **{ + "restrictions": [{"cred_def_id": cd_id}] + for _ in [""] + if cd_id + }, + **{ + "non_revoked": interval.serialize() + for _ in [""] + if revoc_support + }, + } + else: + proof_req["requested_attributes"][ + "{}_{}_uuid".format( + len(proof_req["requested_attributes"]), + canon(attr_spec.name), + ) + ] = { + "name": attr_spec.name, + **{"restrictions": [{"cred_def_id": cd_id}] for _ in [""] if cd_id}, + **{ + "non_revoked": interval.serialize() + for _ in [""] + if revoc_support + }, + } + + for reft, attr_spec in attr_specs_names.items(): + proof_req["requested_attributes"][ + "{}_{}_uuid".format( + len(proof_req["requested_attributes"]), canon(attr_spec["names"][0]) + ) + ] = attr_spec + + for pred_spec in self.predicates: + cd_id = pred_spec.cred_def_id + revoc_support = False + if cd_id: + if profile: + multitenant_mgr = profile.inject_or(BaseMultitenantManager) + if multitenant_mgr: + ledger_exec_inst = IndyLedgerRequestsExecutor(profile) + else: + ledger_exec_inst = profile.inject(IndyLedgerRequestsExecutor) + ledger = ( + await ledger_exec_inst.get_ledger_for_identifier( + cd_id, + txn_record_type=GET_CRED_DEF, + ) + )[1] + if ledger: + async with ledger: + revoc_support = (await ledger.get_credential_definition(cd_id))[ + "value" + ].get("revocation") + interval = non_revoc(cd_id) if revoc_support else None + + proof_req["requested_predicates"][ + "{}_{}_{}_uuid".format( + len(proof_req["requested_predicates"]), + canon(pred_spec.name), + Predicate.get(pred_spec.predicate).value.fortran, + ) + ] = { + "name": pred_spec.name, + "p_type": pred_spec.predicate, + "p_value": pred_spec.threshold, + **{"restrictions": [{"cred_def_id": cd_id}] for _ in [""] if cd_id}, + **{"non_revoked": interval.serialize() for _ in [""] if revoc_support}, + } + + return proof_req + + def __eq__(self, other): + """Equality comparator.""" + + for part in vars(self): + if getattr(self, part, None) != getattr(other, part, None): + return False + return True + + +class IndyPresPreviewSchema(BaseModelSchema): + """Presentation preview schema.""" + + class Meta: + """Presentation preview schema metadata.""" + + model_class = IndyPresPreview + unknown = EXCLUDE + + _type = fields.Str( + description="Message type identifier", + required=False, + example=DIDCommPrefix.qualify_current(PRESENTATION_PREVIEW), + data_key="@type", + ) + attributes = fields.Nested(IndyPresAttrSpecSchema, required=True, many=True) + predicates = fields.Nested(IndyPresPredSpecSchema, required=True, many=True) diff --git a/aries_cloudagent/indy/models/proof.py b/aries_cloudagent/indy/models/proof.py new file mode 100644 index 0000000000..557e7ea71a --- /dev/null +++ b/aries_cloudagent/indy/models/proof.py @@ -0,0 +1,676 @@ +"""Marshmallow bindings for indy proofs.""" + +from typing import Mapping, Sequence + +from marshmallow import EXCLUDE, fields, validate + +from ...messaging.models.base import BaseModel, BaseModelSchema +from ...messaging.valid import ( + INDY_CRED_DEF_ID, + INDY_REV_REG_ID, + INDY_SCHEMA_ID, + INT_EPOCH, + NUM_STR_WHOLE, + NUM_STR_ANY, +) +from ...utils.tracing import AdminAPIMessageTracingSchema + +from .predicate import Predicate +from .requested_creds import ( + IndyRequestedCredsRequestedAttrSchema, + IndyRequestedCredsRequestedPredSchema, +) + + +class IndyEQProof(BaseModel): + """Equality proof for indy primary proof.""" + + class Meta: + """Equality proof metadata.""" + + schema_class = "IndyEQProofMeta" + + def __init__( + self, + revealed_attrs: Mapping[str, str] = None, + a_prime: str = None, + e: str = None, + v: str = None, + m: Mapping[str, str] = None, + m2: 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 IndyEQProofSchema(BaseModelSchema): + """Indy equality proof schema.""" + + class Meta: + """Indy equality proof metadata.""" + + model_class = IndyEQProof + unknown = EXCLUDE + + revealed_attrs = fields.Dict( + keys=fields.Str(example="preference"), + values=fields.Str(**NUM_STR_ANY), + ) + a_prime = fields.Str(**NUM_STR_WHOLE) + e = fields.Str(**NUM_STR_WHOLE) + v = fields.Str(**NUM_STR_WHOLE) + m = fields.Dict( + keys=fields.Str(example="master_secret"), + values=fields.Str(**NUM_STR_WHOLE), + ) + m2 = fields.Str(**NUM_STR_WHOLE) + + +class IndyGEProofPred(BaseModel): + """Indy GE proof predicate.""" + + class Meta: + """Indy GE proof predicate metadata.""" + + schema_class = "IndyGEProofPredSchema" + + def __init__( + self, + attr_name: str = None, + p_type: str = None, + value: int = None, + **kwargs, + ): + """Initialize indy GE proof predicate.""" + super().__init__(**kwargs) + self.attr_name = attr_name + self.p_type = p_type + self.value = value + + +class IndyGEProofPredSchema(BaseModelSchema): + """Indy GE proof predicate schema.""" + + class Meta: + """Indy GE proof predicate metadata.""" + + model_class = IndyGEProofPred + unknown = EXCLUDE + + attr_name = fields.Str(description="Attribute name, indy-canonicalized") + p_type = fields.Str( + description="Predicate type", + validate=validate.OneOf([p.fortran for p in Predicate]), + ) + value = fields.Integer(strict=True, description="Predicate threshold value") + + +class IndyGEProof(BaseModel): + """Greater-than-or-equal-to proof for indy primary proof.""" + + class Meta: + """GE proof metadata.""" + + schema_class = "IndyGEProofMeta" + + def __init__( + self, + u: Mapping[str, str] = None, + r: Mapping[str, str] = None, + mj: str = None, + alpha: str = None, + t: Mapping[str, str] = None, + predicate: IndyGEProofPred = 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 IndyGEProofSchema(BaseModelSchema): + """Indy GE proof schema.""" + + class Meta: + """Indy GE proof schema metadata.""" + + model_class = IndyGEProof + unknown = EXCLUDE + + u = fields.Dict(keys=fields.Str(), values=fields.Str(**NUM_STR_WHOLE)) + r = fields.Dict(keys=fields.Str(), values=fields.Str(**NUM_STR_WHOLE)) + mj = fields.Str(**NUM_STR_WHOLE) + alpha = fields.Str(**NUM_STR_WHOLE) + t = fields.Dict(keys=fields.Str(), values=fields.Str(**NUM_STR_WHOLE)) + predicate = fields.Nested(IndyGEProofPredSchema) + + +class IndyPrimaryProof(BaseModel): + """Indy primary proof.""" + + class Meta: + """Indy primary proof metadata.""" + + schema_class = "IndyPrimaryProofSchema" + + def __init__( + self, + eq_proof: IndyEQProof = None, + ge_proofs: Sequence[IndyGEProof] = None, + **kwargs, + ): + """Initialize indy primary proof.""" + super().__init__(**kwargs) + self.eq_proof = eq_proof + self.ge_proofs = ge_proofs + + +class IndyPrimaryProofSchema(BaseModelSchema): + """Indy primary proof schema.""" + + class Meta: + """Indy primary proof schema metadata.""" + + model_class = IndyPrimaryProof + unknown = EXCLUDE + + eq_proof = fields.Nested( + IndyEQProofSchema, allow_none=True, description="Indy equality proof" + ) + ge_proofs = fields.Nested( + IndyGEProofSchema, + many=True, + allow_none=True, + description="Indy GE proofs", + ) + + +class IndyNonRevocProof(BaseModel): + """Indy non-revocation proof.""" + + class Meta: + """Indy non-revocation proof metadata.""" + + schema_class = "IndyNonRevocProofSchema" + + def __init__( + self, + x_list: Mapping = None, + c_list: Mapping = None, + **kwargs, + ): + """Initialize indy non-revocation proof.""" + super().__init__(**kwargs) + self.x_list = x_list + self.c_list = c_list + + +class IndyNonRevocProofSchema(BaseModelSchema): + """Indy non-revocation proof schema.""" + + class Meta: + """Indy non-revocation proof schema metadata.""" + + model_class = IndyNonRevocProof + unknown = EXCLUDE + + x_list = fields.Dict(keys=fields.Str(), values=fields.Str()) + c_list = fields.Dict(keys=fields.Str(), values=fields.Str()) + + +class IndyProofProofProofsProof(BaseModel): + """Indy proof.proof.proofs constituent proof.""" + + class Meta: + """Indy proof.proof.proofs constituent proof schema.""" + + schema_class = "IndyProofProofProofsProofSchema" + + def __init__( + self, + primary_proof: IndyPrimaryProof = None, + non_revoc_proof: IndyNonRevocProof = None, + **kwargs, + ): + """Initialize proof.proof.proofs constituent proof.""" + super().__init__(**kwargs) + self.primary_proof = primary_proof + self.non_revoc_proof = non_revoc_proof + + +class IndyProofProofProofsProofSchema(BaseModelSchema): + """Indy proof.proof.proofs constituent proof schema.""" + + class Meta: + """Indy proof.proof.proofs constituent proof schema metadata.""" + + model_class = IndyProofProofProofsProof + unknown = EXCLUDE + + primary_proof = fields.Nested( + IndyPrimaryProofSchema, + description="Indy primary proof", + ) + non_revoc_proof = fields.Nested( + IndyNonRevocProofSchema, + allow_none=True, + description="Indy non-revocation proof", + ) + + +class IndyProofProofAggregatedProof(BaseModel): + """Indy proof.proof aggregated proof.""" + + class Meta: + """Indy proof.proof aggregated proof metadata.""" + + schema_class = "IndyProofProofAggregatedProofSchema" + + def __init__( + self, + c_hash: str = None, + c_list: Sequence[Sequence[int]] = None, + **kwargs, + ): + """Initialize indy proof.proof agreggated proof.""" + super().__init__(**kwargs) + self.c_hash = c_hash + self.c_list = c_list + + +class IndyProofProofAggregatedProofSchema(BaseModelSchema): + """Indy proof.proof aggregated proof schema.""" + + class Meta: + """Indy proof.proof aggregated proof schema metadata.""" + + model_class = IndyProofProofAggregatedProof + unknown = EXCLUDE + + c_hash = fields.Str(description="c_hash value") + c_list = fields.List( + fields.List(fields.Int(strict=True)), + description="c_list value", + ) + + +class IndyProofProof(BaseModel): + """Indy proof.proof content.""" + + class Meta: + """Indy proof.proof content metadata.""" + + schema_class = "IndyProofProofSchema" + + def __init__( + self, + proofs: Sequence[IndyProofProofProofsProof] = None, + aggregated_proof: IndyProofProofAggregatedProof = None, + **kwargs, + ): + """Initialize indy proof.proof content.""" + super().__init__(**kwargs) + self.proofs = proofs + self.aggregated_proof = aggregated_proof + + +class IndyProofProofSchema(BaseModelSchema): + """Indy proof.proof content schema.""" + + class Meta: + """Indy proof.proof content schema metadata.""" + + model_class = IndyProofProof + unknown = EXCLUDE + + proofs = fields.Nested( + IndyProofProofProofsProofSchema, + many=True, + description="Indy proof proofs", + ) + aggregated_proof = fields.Nested( + IndyProofProofAggregatedProofSchema, + description="Indy 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: str = None, + encoded: 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(description="Raw value") + encoded = fields.Str(description="Encoded value", **NUM_STR_ANY) + + +class IndyProofRequestedProofRevealedAttr(RawEncoded): + """Indy proof requested proof revealed attr.""" + + class Meta: + """Indy proof requested proof revealed attr metadata.""" + + schema_class = "IndyProofRequestedProofRevealedAttrSchema" + + def __init__( + self, + sub_proof_index: int = None, + **kwargs, + ): + """Initialize indy proof requested proof revealed attr.""" + super().__init__(**kwargs) + self.sub_proof_index = sub_proof_index + + +class IndyProofRequestedProofRevealedAttrSchema(RawEncodedSchema): + """Indy proof requested proof revealed attr schema.""" + + class Meta: + """Indy proof requested proof revealed attr schema metadata.""" + + model_class = IndyProofRequestedProofRevealedAttr + unknown = EXCLUDE + + sub_proof_index = fields.Int(strict=True, description="Sub-proof index") + + +class IndyProofRequestedProofRevealedAttrGroup(BaseModel): + """Indy proof requested proof revealed attr group.""" + + class Meta: + """Indy proof requested proof revealed attr group metadata.""" + + schema_class = "IndyProofRequestedProofRevealedAttrGroupSchema" + + def __init__( + self, + sub_proof_index: int = None, + values: Mapping[str, RawEncoded] = None, + **kwargs, + ): + """Initialize indy proof requested proof revealed attr.""" + super().__init__(**kwargs) + self.sub_proof_index = sub_proof_index + self.values = values + + +class IndyProofRequestedProofRevealedAttrGroupSchema(BaseModelSchema): + """Indy proof requested proof revealed attr group schema.""" + + class Meta: + """Indy proof requested proof revealed attr group schema metadata.""" + + model_class = IndyProofRequestedProofRevealedAttrGroup + unknown = EXCLUDE + + sub_proof_index = fields.Int(strict=True, description="Sub-proof index") + values = fields.Dict( + keys=fields.Str(), + values=fields.Nested(RawEncodedSchema), + description="Indy proof requested proof revealed attr groups group value", + ) + + +class IndyProofRequestedProofPredicate(BaseModel): + """Indy proof requested proof predicate.""" + + class Meta: + """Indy proof requested proof requested proof predicate metadata.""" + + schema_class = "IndyProofRequestedProofPredicateSchema" + + def __init__( + self, + sub_proof_index: int = None, + **kwargs, + ): + """Initialize indy proof requested proof predicate.""" + super().__init__(**kwargs) + self.sub_proof_index = sub_proof_index + + +class IndyProofRequestedProofPredicateSchema(BaseModelSchema): + """Indy proof requested prrof predicate schema.""" + + class Meta: + """Indy proof requested proof requested proof predicate schema metadata.""" + + model_class = IndyProofRequestedProofPredicate + unknown = EXCLUDE + + sub_proof_index = fields.Int(strict=True, description="Sub-proof index") + + +class IndyProofRequestedProof(BaseModel): + """Indy proof.requested_proof content.""" + + class Meta: + """Indy proof.requested_proof content metadata.""" + + schema_class = "IndyProofRequestedProofSchema" + + def __init__( + self, + revealed_attrs: Mapping[str, IndyProofRequestedProofRevealedAttr] = None, + revealed_attr_groups: Mapping[ + str, + IndyProofRequestedProofRevealedAttrGroup, + ] = None, + self_attested_attrs: Mapping = None, + unrevealed_attrs: Mapping = None, + predicates: Mapping[str, IndyProofRequestedProofPredicate] = None, + **kwargs, + ): + """Initialize indy 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 IndyProofRequestedProofSchema(BaseModelSchema): + """Indy proof requested proof schema.""" + + class Meta: + """Indy proof requested proof schema metadata.""" + + model_class = IndyProofRequestedProof + unknown = EXCLUDE + + revealed_attrs = fields.Dict( + keys=fields.Str(), + values=fields.Nested(IndyProofRequestedProofRevealedAttrSchema), + allow_none=True, + description="Proof requested proof revealed attributes", + ) + revealed_attr_groups = fields.Dict( + keys=fields.Str(), + values=fields.Nested(IndyProofRequestedProofRevealedAttrGroupSchema), + allow_none=True, + description="Proof requested proof revealed attribute groups", + ) + self_attested_attrs = fields.Dict( + description="Proof requested proof self-attested attributes" + ) + unrevealed_attrs = fields.Dict(description="Unrevealed attributes") + predicates = fields.Dict( + keys=fields.Str(), + values=fields.Nested( + IndyProofRequestedProofPredicateSchema, + ), + description="Proof requested proof predicates.", + ) + + +class IndyProofIdentifier(BaseModel): + """Indy proof identifier.""" + + class Meta: + """Indy proof identifier metadata.""" + + schema_class = "IndyProofIdentifierSchema" + + def __init__( + self, + schema_id: str = None, + cred_def_id: str = None, + rev_reg_id: str = None, + timestamp: int = None, + **kwargs, + ): + """Initialize indy 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 IndyProofIdentifierSchema(BaseModelSchema): + """Indy proof identifier schema.""" + + class Meta: + """Indy proof identifier schema metadata.""" + + model_class = IndyProofIdentifier + unknown = EXCLUDE + + schema_id = fields.Str(description="Schema identifier", **INDY_SCHEMA_ID) + cred_def_id = fields.Str( + description="Credential definition identifier", + **INDY_CRED_DEF_ID, + ) + rev_reg_id = fields.Str( + description="Revocation registry identifier", + allow_none=True, + **INDY_REV_REG_ID, + ) + timestamp = fields.Int( + strict=True, + allow_none=True, + description="Timestamp epoch", + **INT_EPOCH, + ) + + +class IndyProof(BaseModel): + """Indy proof.""" + + class Meta: + """Indy proof metadata.""" + + schema_class = "IndyProofSchema" + + def __init__( + self, + proof: IndyProofProof = None, + requested_proof: IndyProofRequestedProof = None, + identifiers: Sequence[IndyProofIdentifier] = None, + **kwargs, + ): + """Initialize indy proof.""" + super().__init__(**kwargs) + self.proof = proof + self.requested_proof = requested_proof + self.identifiers = identifiers + + +class IndyProofSchema(BaseModelSchema): + """Indy proof schema.""" + + class Meta: + """Indy proof schema metadata.""" + + model_class = IndyProof + unknown = EXCLUDE + + proof = fields.Nested( + IndyProofProofSchema, + description="Indy proof.proof content", + ) + requested_proof = fields.Nested( + IndyProofRequestedProofSchema, + description="Indy proof.requested_proof content", + ) + identifiers = fields.Nested( + IndyProofIdentifierSchema, + many=True, + description="Indy proof.identifiers content", + ) + + +class IndyPresSpecSchema(AdminAPIMessageTracingSchema): + """Request schema for indy proof specification to send as presentation.""" + + self_attested_attributes = fields.Dict( + description="Self-attested attributes to build into proof", + required=True, + keys=fields.Str(example="attr_name"), # marshmallow/apispec v3.0 ignores + values=fields.Str( + example="self_attested_value", + description=( + "Self-attested attribute values to use in requested-credentials " + "structure for proof construction" + ), + ), + ) + requested_attributes = fields.Dict( + description=( + "Nested object mapping proof request attribute referents to " + "requested-attribute specifiers" + ), + required=True, + keys=fields.Str(example="attr_referent"), # marshmallow/apispec v3.0 ignores + values=fields.Nested(IndyRequestedCredsRequestedAttrSchema), + ) + requested_predicates = fields.Dict( + description=( + "Nested object mapping proof request predicate referents to " + "requested-predicate specifiers" + ), + required=True, + keys=fields.Str(example="pred_referent"), # marshmallow/apispec v3.0 ignores + values=fields.Nested(IndyRequestedCredsRequestedPredSchema), + ) + trace = fields.Bool( + description="Whether to trace event (default false)", + required=False, + example=False, + ) diff --git a/aries_cloudagent/indy/models/proof_request.py b/aries_cloudagent/indy/models/proof_request.py new file mode 100644 index 0000000000..a0623b6530 --- /dev/null +++ b/aries_cloudagent/indy/models/proof_request.py @@ -0,0 +1,260 @@ +"""Utilities to deal with indy.""" + +from typing import Mapping + +from marshmallow import ( + EXCLUDE, + fields, + Schema, + validate, + validates_schema, + ValidationError, +) + +from ...messaging.models.base import BaseModel, BaseModelSchema +from ...messaging.models.openapi import OpenAPISchema +from ...messaging.valid import ( + INDY_CRED_DEF_ID, + INDY_PREDICATE, + INDY_VERSION, + INT_EPOCH, + NUM_STR_NATURAL, +) + + +class IndyProofReqAttrSpecSchema(OpenAPISchema): + """Schema for attribute specification in indy proof request.""" + + name = fields.Str( + example="favouriteDrink", description="Attribute name", required=False + ) + names = fields.List( + fields.Str(example="age"), + description="Attribute name group", + required=False, + ) + 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$" # indy does not support attr::...::marker here + ), + example="cred_def_id", # marshmallow/apispec v3.0 ignores + ), + values=fields.Str(example=INDY_CRED_DEF_ID["example"]), + ), + 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" + ), + required=False, + ) + non_revoked = fields.Nested( + Schema.from_dict( + { + "from": fields.Int( + required=False, + description="Earliest time of interest in non-revocation interval", + strict=True, + **INT_EPOCH, + ), + "to": fields.Int( + required=False, + description="Latest time of interest in non-revocation interval", + strict=True, + **INT_EPOCH, + ), + }, + name="IndyProofReqAttrSpecNonRevokedSchema", + ), + allow_none=True, # accommodate libvcx + 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 + + 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 IndyProofReqPredSpecSchema(OpenAPISchema): + """Schema for predicate specification in indy proof request.""" + + name = fields.Str(example="index", description="Attribute name", required=True) + p_type = fields.Str( + description="Predicate type ('<', '<=', '>=', or '>')", + required=True, + **INDY_PREDICATE, + ) + p_value = fields.Int(description="Threshold value", required=True, 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$" # indy does not support attr::...::marker here + ), + example="cred_def_id", + ), + values=fields.Str(example=INDY_CRED_DEF_ID["example"]), + ), + 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" + ), + required=False, + ) + non_revoked = fields.Nested( + Schema.from_dict( + { + "from": fields.Int( + required=False, + description="Earliest time of interest in non-revocation interval", + strict=True, + **INT_EPOCH, + ), + "to": fields.Int( + required=False, + description="Latest time of interest in non-revocation interval", + strict=True, + **INT_EPOCH, + ), + }, + name="IndyProofReqPredSpecNonRevokedSchema", + ), + allow_none=True, # accommodate libvcx + required=False, + ) + + +class IndyProofRequest(BaseModel): + """Indy proof request.""" + + class Meta: + """Indy proof request metadata.""" + + schema_class = "IndyProofRequestSchema" + + def __init__( + self, + nonce: str = None, + name: str = None, + version: str = None, + requested_attributes: Mapping = None, + requested_predicates: Mapping = None, + non_revoked: Mapping = None, + **kwargs, + ): + """ + Initialize indy cred abstract object. + + Args: + schema_id: schema identifier + cred_def_id: credential definition identifier + nonce: nonce + key_correctness_proof: key correctness proof + + """ + 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 IndyProofRequestSchema(BaseModelSchema): + """Schema for indy proof request.""" + + class Meta: + """Indy proof request schema metadata.""" + + model_class = IndyProofRequest + unknown = EXCLUDE + + nonce = fields.Str( + description="Nonce", + required=False, + **NUM_STR_NATURAL, + ) + name = fields.Str( + description="Proof request name", + required=False, + example="Proof request", + default="Proof request", + ) + version = fields.Str( + description="Proof request version", + required=False, + default="1.0", + **INDY_VERSION, + ) + requested_attributes = fields.Dict( + description="Requested attribute specifications of proof request", + required=True, + keys=fields.Str(decription="Attribute referent", example="0_legalname_uuid"), + values=fields.Nested(IndyProofReqAttrSpecSchema()), + ) + requested_predicates = fields.Dict( + description="Requested predicate specifications of proof request", + required=True, + keys=fields.Str(description="Predicate referent", example="0_age_GE_uuid"), + values=fields.Nested(IndyProofReqPredSpecSchema()), + ) + non_revoked = fields.Nested( + Schema.from_dict( + { + "from": fields.Int( + required=False, + description="Earliest time of interest in non-revocation interval", + strict=True, + **INT_EPOCH, + ), + "to": fields.Int( + required=False, + description="Latest time of interest in non-revocation interval", + strict=True, + **INT_EPOCH, + ), + }, + name="IndyProofRequestNonRevokedSchema", + ), + allow_none=True, # accommodate libvcx + required=False, + ) diff --git a/aries_cloudagent/indy/models/requested_creds.py b/aries_cloudagent/indy/models/requested_creds.py new file mode 100644 index 0000000000..074ec2c140 --- /dev/null +++ b/aries_cloudagent/indy/models/requested_creds.py @@ -0,0 +1,39 @@ +"""Admin routes for presentations.""" + +from marshmallow import fields + +from ...messaging.models.openapi import OpenAPISchema +from ...messaging.valid import INT_EPOCH + + +class IndyRequestedCredsRequestedAttrSchema(OpenAPISchema): + """Schema for requested attributes within indy requested credentials structure.""" + + cred_id = fields.Str( + example="3fa85f64-5717-4562-b3fc-2c963f66afa6", + description=( + "Wallet credential identifier (typically but not necessarily a UUID)" + ), + required=True, + ) + revealed = fields.Bool( + description="Whether to reveal attribute in proof (default true)", default=True + ) + + +class IndyRequestedCredsRequestedPredSchema(OpenAPISchema): + """Schema for requested predicates within indy requested credentials structure.""" + + cred_id = fields.Str( + description=( + "Wallet credential identifier (typically but not necessarily a UUID)" + ), + example="3fa85f64-5717-4562-b3fc-2c963f66afa6", + required=True, + ) + timestamp = fields.Int( + description="Epoch timestamp of interest for non-revocation proof", + required=False, + strict=True, + **INT_EPOCH, + ) diff --git a/aries_cloudagent/indy/models/revocation.py b/aries_cloudagent/indy/models/revocation.py new file mode 100644 index 0000000000..fdc90008ae --- /dev/null +++ b/aries_cloudagent/indy/models/revocation.py @@ -0,0 +1,272 @@ +"""Revocation artifacts.""" + +from typing import Sequence + +from marshmallow import EXCLUDE, fields, validate + +from ...messaging.models.base import BaseModel, BaseModelSchema +from ...messaging.valid import ( + BASE58_SHA256_HASH, + INDY_CRED_DEF_ID, + INDY_REV_REG_ID, + INDY_VERSION, + NATURAL_NUM, +) + + +class IndyRevRegDefValuePublicKeysAccumKey(BaseModel): + """Indy revocation registry definition value public keys accum key.""" + + class Meta: + """Indy revocation registry definition value public keys accum key metadata.""" + + schema_class = "IndyRevRegDefValuePublicKeysAccumKeySchema" + + def __init__(self, z: str = None): + """Initialize.""" + + self.z = z + + +class IndyRevRegDefValuePublicKeysAccumKeySchema(BaseModelSchema): + """Indy revocation registry definition value public keys accum key schema.""" + + class Meta: + """Schema metadata.""" + + model_class = IndyRevRegDefValuePublicKeysAccumKey + unknown = EXCLUDE + + z = fields.Str( + description="Value for z", example="1 120F522F81E6B7 1 09F7A59005C4939854" + ) + + +class IndyRevRegDefValuePublicKeys(BaseModel): + """Indy revocation registry definition value public keys.""" + + class Meta: + """Model metadata.""" + + schema_class = "IndyRevRegDefValuePublicKeysSchema" + + def __init__(self, accum_key: IndyRevRegDefValuePublicKeysAccumKey = None): + """Initialize.""" + + self.accum_key = accum_key + + +class IndyRevRegDefValuePublicKeysSchema(BaseModelSchema): + """Indy revocation registry definition value public keys schema.""" + + class Meta: + """Schema metadata.""" + + model_class = IndyRevRegDefValuePublicKeys + unknown = EXCLUDE + + accum_key = fields.Nested( + IndyRevRegDefValuePublicKeysAccumKeySchema(), data_key="accumKey" + ) + + +class IndyRevRegDefValue(BaseModel): + """Indy revocation registry definition value.""" + + class Meta: + """Model metadata.""" + + schema_class = "IndyRevRegDefValueSchema" + + def __init__( + self, + issuance_type: str = None, + max_cred_num: int = None, + public_keys: IndyRevRegDefValuePublicKeys = None, + tails_hash: str = None, + tails_location: str = None, + ): + """Initialize.""" + self.issuance_type = issuance_type + self.max_cred_num = max_cred_num + self.public_keys = public_keys + self.tails_hash = tails_hash + self.tails_location = tails_location + + +class IndyRevRegDefValueSchema(BaseModelSchema): + """Indy revocation registry definition value schema.""" + + class Meta: + """Schema metadata.""" + + model_class = IndyRevRegDefValue + unknown = EXCLUDE + + issuance_type = fields.Str( + validate=validate.OneOf(["ISSUANCE_ON_DEMAND", "ISSUANCE_BY_DEFAULT"]), + data_key="issuanceType", + description="Issuance type", + ) + max_cred_num = fields.Int( + description="Maximum number of credentials; registry size", + strict=True, + data_key="maxCredNum", + **NATURAL_NUM, + ) + public_keys = fields.Nested( + IndyRevRegDefValuePublicKeysSchema(), + data_key="publicKeys", + description="Public keys", + ) + tails_hash = fields.Str( + data_key="tailsHash", + description="Tails hash value", + **BASE58_SHA256_HASH, + ) + tails_location = fields.Str( + description="Tails file location", + data_key="tailsLocation", + ) + + +class IndyRevRegDef(BaseModel): + """Indy revocation registry definition.""" + + class Meta: + """Model metadata.""" + + schema_class = "IndyRevRegDefSchema" + + def __init__( + self, + ver: str = None, + id_: str = None, + revoc_def_type: str = None, + tag: str = None, + cred_def_id: str = None, + value: IndyRevRegDefValue = None, + ): + """Initialize.""" + + self.ver = ver + self.id_ = id_ + self.revoc_def_type = revoc_def_type + self.tag = tag + self.cred_def_id = cred_def_id + self.value = value + + +class IndyRevRegDefSchema(BaseModelSchema): + """Indy revocation registry definition schema.""" + + class Meta: + """Schema metadata.""" + + model_class = IndyRevRegDef + unknown = EXCLUDE + + ver = fields.Str( + description="Version of revocation registry definition", + **INDY_VERSION, + ) + id_ = fields.Str( + description="Indy revocation registry identifier", + data_key="id", + **INDY_REV_REG_ID, + ) + revoc_def_type = fields.Str( + description="Revocation registry type (specify CL_ACCUM)", + data_key="revocDefType", + example="CL_ACCUM", + validate=validate.Equal("CL_ACCUM"), + ) + tag = fields.Str(description="Revocation registry tag") + cred_def_id = fields.Str( + data_key="credDefId", + description="Credential definition identifier", + **INDY_CRED_DEF_ID, + ) + value = fields.Nested( + IndyRevRegDefValueSchema(), description="Revocation registry definition value" + ) + + +class IndyRevRegEntryValue(BaseModel): + """Indy revocation registry entry value.""" + + class Meta: + """Model metadata.""" + + schema_class = "IndyRevRegEntryValueSchema" + + def __init__( + self, + prev_accum: str = None, + accum: str = None, + revoked: Sequence[int] = None, + ): + """Initialize.""" + self.prev_accum = prev_accum + self.accum = accum + self.revoked = revoked + + +class IndyRevRegEntryValueSchema(BaseModelSchema): + """Indy revocation registry entry value schema.""" + + class Meta: + """Schema metadata.""" + + model_class = "IndyRevRegEntryValue" + unknown = EXCLUDE + + prev_accum = fields.Str( + description="Previous accumulator value", + data_key="prevAccum", + required=False, + example="21 137AC810975E4 6 76F0384B6F23", + ) + accum = fields.Str( + description="Accumulator value", + example="21 11792B036AED0AAA12A4 4 298B2571FFC63A737", + ) + revoked = fields.List( + fields.Int(strict=True), + required=False, + description="Revoked credential revocation identifiers", + ) + + +class IndyRevRegEntry(BaseModel): + """Indy revocation registry entry.""" + + class Meta: + """Model metadata.""" + + schema_class = "IndyRevRegEntrySchema" + + def __init__(self, ver: str = None, value: IndyRevRegEntryValue = None): + """Initialize.""" + + self.ver = ver + self.value = value + + +class IndyRevRegEntrySchema(BaseModelSchema): + """Indy revocation registry entry schema.""" + + class Meta: + """Schema metadata.""" + + model_class = IndyRevRegEntry + unknown = EXCLUDE + + ver = fields.Str( + description="Version of revocation registry entry", + **INDY_VERSION, + ) + value = fields.Nested( + IndyRevRegEntryValueSchema(), + description="Revocation registry entry value", + ) diff --git a/aries_cloudagent/indy/models/schema.py b/aries_cloudagent/indy/models/schema.py new file mode 100644 index 0000000000..4a48aaf1b3 --- /dev/null +++ b/aries_cloudagent/indy/models/schema.py @@ -0,0 +1,27 @@ +"""Schema artifacts.""" + +from marshmallow import fields + +from ...messaging.models.openapi import OpenAPISchema +from ...messaging.valid import INDY_SCHEMA_ID, INDY_VERSION, NATURAL_NUM + + +class SchemaSchema(OpenAPISchema): + """Marshmallow schema for indy schema.""" + + ver = fields.Str(description="Node protocol version", **INDY_VERSION) + ident = fields.Str(data_key="id", description="Schema identifier", **INDY_SCHEMA_ID) + name = fields.Str( + description="Schema name", + example=INDY_SCHEMA_ID["example"].split(":")[2], + ) + version = fields.Str(description="Schema version", **INDY_VERSION) + attr_names = fields.List( + fields.Str( + description="Attribute name", + example="score", + ), + description="Schema attribute names", + data_key="attrNames", + ) + seqNo = fields.Int(description="Schema sequence number", strict=True, **NATURAL_NUM) diff --git a/aries_cloudagent/indy/models/tests/__init__.py b/aries_cloudagent/indy/models/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aries_cloudagent/indy/models/tests/test_cred.py b/aries_cloudagent/indy/models/tests/test_cred.py new file mode 100644 index 0000000000..ef6d106cc5 --- /dev/null +++ b/aries_cloudagent/indy/models/tests/test_cred.py @@ -0,0 +1,126 @@ +from unittest import TestCase + +from ..cred import IndyAttrValue, IndyCredential +from ..cred_abstract import IndyCredAbstract, IndyKeyCorrectnessProof +from ..cred_request import IndyCredRequest + +KC_PROOF = { + "c": "123467890", + "xz_cap": "12345678901234567890", + "xr_cap": [ + [ + "remainder", + "1234567890", + ], + [ + "number", + "12345678901234", + ], + [ + "master_secret", + "12345678901234", + ], + ], +} + +TEST_DID = "LjgpST2rjsoxYegQDRm7EL" +SCHEMA_NAME = "bc-reg" +SCHEMA_TXN = 12 +SCHEMA_ID = f"{TEST_DID}:2:{SCHEMA_NAME}:1.0" +CRED_DEF_ID = f"{TEST_DID}:3:CL:12:default" + + +class TestIndyKeyCorrectnessProof(TestCase): + """Indy key correctness proof tests""" + + def test_serde(self): + """Test de/serialization.""" + kcp = IndyKeyCorrectnessProof.deserialize(KC_PROOF) + assert type(kcp) == IndyKeyCorrectnessProof + + kcp_dict = kcp.serialize() + assert kcp_dict == KC_PROOF + + +class TestIndyCredAbstract(TestCase): + """Indy cred abstract tests.""" + + def test_serde(self): + """Test de/serialization.""" + obj = { + "schema_id": SCHEMA_ID, + "cred_def_id": CRED_DEF_ID, + "nonce": "1234567890", + "key_correctness_proof": KC_PROOF, + } + cred_abstract = IndyCredAbstract.deserialize(obj) + assert type(cred_abstract) == IndyCredAbstract + + cred_abstract_dict = cred_abstract.serialize() + assert cred_abstract_dict == obj + + +class TestIndyCredRequest(TestCase): + """Indy cred request tests.""" + + def test_serde(self): + """Test de/serialization.""" + obj = { + "prover_did": f"did:sov:{TEST_DID}", + "cred_def_id": CRED_DEF_ID, + "blinded_ms": {"...": "..."}, + "blinded_ms_correctness_proof": {"...": "..."}, + "nonce": "1234567890", + } + cred_request = IndyCredRequest.deserialize(obj) + assert type(cred_request) == IndyCredRequest + + cred_request_dict = cred_request.serialize() + assert cred_request_dict == obj + + +class TestIndyAttrValue(TestCase): + """Indy attr value tests.""" + + def test_serde(self): + """Test de/serialization.""" + obj = { + "raw": "test", + "encoded": "1234567890", + } + attr_val = IndyAttrValue.deserialize(obj) + assert type(attr_val) == IndyAttrValue + + attr_val_dict = attr_val.serialize() + assert attr_val_dict == obj + + +class TestIndyCredential(TestCase): + """Indy credential tests.""" + + def test_serde(self): + """Test de/serialization.""" + obj = { + "schema_id": SCHEMA_ID, + "cred_def_id": CRED_DEF_ID, + "rev_reg_id": None, + "values": { + "busId": { + "raw": "12345", + "encoded": "12345", + }, + "legalName": { + "raw": "Muffin Moon", + "encoded": "13419834198651328645901659586128164", + }, + }, + "signature": {"...": "..."}, + "signature_correctness_proof": {"...": "..."}, + "rev_reg": None, + "witness": None, + } + cred = IndyCredential.deserialize(obj) + assert type(cred) == IndyCredential + + cred_dict = cred.serialize() + assert cred_dict.items() <= obj.items() diff --git a/aries_cloudagent/indy/models/tests/test_cred_precis.py b/aries_cloudagent/indy/models/tests/test_cred_precis.py new file mode 100644 index 0000000000..3a025a7a82 --- /dev/null +++ b/aries_cloudagent/indy/models/tests/test_cred_precis.py @@ -0,0 +1,43 @@ +from unittest import TestCase + +from ..cred_precis import IndyCredInfo, IndyCredInfoSchema + +TEST_DID = "LjgpST2rjsoxYegQDRm7EL" +SCHEMA_NAME = "preferences" +SCHEMA_TXN = 12 +SCHEMA_ID = f"{TEST_DID}:2:{SCHEMA_NAME}:1.0" +CRED_DEF_ID = f"{TEST_DID}:3:CL:12:default" +REV_REG_ID = f"{TEST_DID}:4:{CRED_DEF_ID}:CL_ACCUM:0" + +CRED_INFO = { + "referent": "abc123", + "attrs": { + "userid": "alice", + "dob": "2000-01-01", + "favourite_colour": "purple", + }, + "schema_id": SCHEMA_ID, + "cred_def_id": CRED_DEF_ID, + "rev_reg_id": REV_REG_ID, + "cred_rev_id": "1", +} + + +class TestCredInfo(TestCase): + """Indy cred info tests""" + + def test_serde(self): + """Test de/serialization.""" + cred_info = IndyCredInfo.deserialize(CRED_INFO) + assert type(cred_info) == IndyCredInfo + + ser = cred_info.serialize() + assert ser == CRED_INFO + + deser = IndyCredInfo.deserialize(ser) + reser = deser.serialize() + assert ser == reser + + obj = IndyCredInfo(**CRED_INFO) + ser = obj.serialize() + assert ser == reser diff --git a/aries_cloudagent/indy/models/tests/test_non_rev_interval.py b/aries_cloudagent/indy/models/tests/test_non_rev_interval.py new file mode 100644 index 0000000000..bf80447e35 --- /dev/null +++ b/aries_cloudagent/indy/models/tests/test_non_rev_interval.py @@ -0,0 +1,44 @@ +from unittest import TestCase + +from asynctest import TestCase as AsyncTestCase +from asynctest import mock as async_mock + +import pytest + +from ..non_rev_interval import IndyNonRevocationInterval + +FROM = 1000000000 +TO = 1234567890 + +INTERVAL_FROM = IndyNonRevocationInterval(fro=FROM) +INTERVAL_TO = IndyNonRevocationInterval(to=TO) +INTERVAL = IndyNonRevocationInterval(fro=FROM, to=TO) + + +class TestInterval(TestCase): + """Non-revocation interval tests""" + + def test_serde(self): + """Test serialization and deserialization.""" + for interval in (INTERVAL_FROM, INTERVAL_TO, INTERVAL): + non_revo_dict = interval.serialize() + assert non_revo_dict.get("from") == interval.fro + assert non_revo_dict.get("to") == interval.to + + model = IndyNonRevocationInterval.deserialize(non_revo_dict) + assert model.fro == interval.fro and model.to == interval.to + assert model.timestamp() + + def test_covers(self): + """Test spanning check.""" + assert INTERVAL_FROM.covers(FROM) + assert INTERVAL_FROM.covers(TO) + assert INTERVAL_FROM.covers() + + assert INTERVAL_TO.covers(FROM) + assert INTERVAL_TO.covers(TO) + assert not INTERVAL_TO.covers() + + assert INTERVAL.covers(FROM) + assert INTERVAL.covers(TO) + assert not INTERVAL.covers() diff --git a/aries_cloudagent/indy/models/tests/test_pred.py b/aries_cloudagent/indy/models/tests/test_pred.py new file mode 100644 index 0000000000..a28956b50e --- /dev/null +++ b/aries_cloudagent/indy/models/tests/test_pred.py @@ -0,0 +1,49 @@ +from unittest import TestCase + +from ..predicate import Predicate + + +class TestPredicate(TestCase): + """Predicate tests for coverage""" + + def test_get_monikers(self): + """Get predicate.""" + assert Predicate.get("LT") == Predicate.get("$lt") == Predicate.get("<") + assert Predicate.get("LE") == Predicate.get("$lte") == Predicate.get("<=") + assert Predicate.get("GE") == Predicate.get("$gte") == Predicate.get(">=") + assert Predicate.get("GT") == Predicate.get("$gt") == Predicate.get(">") + assert Predicate.get("!=") is None + + assert Predicate.get("LT").fortran == "LT" + assert Predicate.get("LT").wql == "$lt" + assert Predicate.get("LT").math == "<" + + def test_cmp(self): + """Test comparison via predicates""" + assert Predicate.get("LT").value.yes(0, 1) + assert Predicate.get("LT").value.yes("0", "1") + assert Predicate.get("LT").value.no(0, 0) + assert Predicate.get("LT").value.no(1, 0) + assert Predicate.get("LT").value.no("1", "0") + assert Predicate.get("LT").value.no("0", "0") + + assert Predicate.get("LE").value.yes(0, 1) + assert Predicate.get("LE").value.yes("0", "1") + assert Predicate.get("LE").value.yes(0, 0) + assert Predicate.get("LE").value.no(1, 0) + assert Predicate.get("LE").value.no("1", "0") + assert Predicate.get("LE").value.yes("0", "0") + + assert Predicate.get("GE").value.no(0, 1) + assert Predicate.get("GE").value.no("0", "1") + assert Predicate.get("GE").value.yes(0, 0) + assert Predicate.get("GE").value.yes(1, 0) + assert Predicate.get("GE").value.yes("1", "0") + assert Predicate.get("GE").value.yes("0", "0") + + assert Predicate.get("GT").value.no(0, 1) + assert Predicate.get("GT").value.no("0", "1") + assert Predicate.get("GT").value.no(0, 0) + assert Predicate.get("GT").value.yes(1, 0) + assert Predicate.get("GT").value.yes("1", "0") + assert Predicate.get("GT").value.no("0", "0") diff --git a/aries_cloudagent/indy/models/tests/test_pres_preview.py b/aries_cloudagent/indy/models/tests/test_pres_preview.py new file mode 100644 index 0000000000..5f5809b889 --- /dev/null +++ b/aries_cloudagent/indy/models/tests/test_pres_preview.py @@ -0,0 +1,586 @@ +import json +import pytest + +from copy import deepcopy +from time import time +from unittest import TestCase + +from asynctest import TestCase as AsyncTestCase +from asynctest import mock as async_mock + +from ....core.in_memory import InMemoryProfile +from ....ledger.multiple_ledger.ledger_requests_executor import ( + IndyLedgerRequestsExecutor, +) +from ....messaging.util import canon +from ....multitenant.base import BaseMultitenantManager +from ....multitenant.manager import MultitenantManager +from ....protocols.didcomm_prefix import DIDCommPrefix + + +from ..non_rev_interval import IndyNonRevocationInterval +from ..predicate import Predicate +from ..pres_preview import ( + IndyPresAttrSpec, + IndyPresPredSpec, + IndyPresPreview, + PRESENTATION_PREVIEW, +) + +S_ID = { + "score": "NcYxiDXkpYi6ov5FcYDi1e:2:score:1.0", + "membership": "NcYxiDXkpYi6ov5FcYDi1e:2:membership:1.0", +} +CD_ID = {name: f"NcYxiDXkpYi6ov5FcYDi1e:3:CL:{S_ID[name]}:tag1" for name in S_ID} +PRES_PREVIEW = IndyPresPreview( + attributes=[ + IndyPresAttrSpec( + name="player", cred_def_id=CD_ID["score"], value="Richie Knucklez" + ), + IndyPresAttrSpec( + name="screenCapture", + cred_def_id=CD_ID["score"], + mime_type="image/png", + value="aW1hZ2luZSBhIHNjcmVlbiBjYXB0dXJl", + ), + ], + predicates=[ + IndyPresPredSpec( + name="highScore", + cred_def_id=CD_ID["score"], + predicate=">=", + threshold=1000000, + ) + ], +) +PRES_PREVIEW_ATTR_NAMES = IndyPresPreview( + attributes=[ + IndyPresAttrSpec( + name="player", + cred_def_id=CD_ID["score"], + value="Richie Knucklez", + referent="reft-0", + ), + IndyPresAttrSpec( + name="screenCapture", + cred_def_id=CD_ID["score"], + mime_type="image/png", + value="aW1hZ2luZSBhIHNjcmVlbiBjYXB0dXJl", + referent="reft-0", + ), + IndyPresAttrSpec( + name="member", + cred_def_id=CD_ID["membership"], + value="Richard Hand", + referent="reft-1", + ), + IndyPresAttrSpec( + name="since", + cred_def_id=CD_ID["membership"], + value="2020-01-01", + referent="reft-1", + ), + ] +) +INDY_PROOF_REQ = json.loads( + f"""{{ + "name": "proof-req", + "version": "1.0", + "nonce": "12345", + "requested_attributes": {{ + "0_player_uuid": {{ + "name": "player", + "restrictions": [ + {{ + "cred_def_id": "{CD_ID['score']}" + }} + ] + }}, + "1_screencapture_uuid": {{ + "name": "screenCapture", + "restrictions": [ + {{ + "cred_def_id": "{CD_ID['score']}" + }} + ] + }} + }}, + "requested_predicates": {{ + "0_highscore_GE_uuid": {{ + "name": "highScore", + "p_type": ">=", + "p_value": 1000000, + "restrictions": [ + {{ + "cred_def_id": "{CD_ID['score']}" + }} + ] + }} + }} +}}""" +) +INDY_PROOF_REQ_ATTR_NAMES = json.loads( + f"""{{ + "name": "proof-req", + "version": "1.0", + "nonce": "12345", + "requested_attributes": {{ + "0_player_uuid": {{ + "names": ["player", "screenCapture"], + "restrictions": [ + {{ + "cred_def_id": "{CD_ID['score']}" + }} + ] + }}, + "1_member_uuid": {{ + "names": ["member", "since"], + "restrictions": [ + {{ + "cred_def_id": "{CD_ID['membership']}" + }} + ] + }} + }}, + "requested_predicates": {{}} +}}""" +) + + +class TestIndyPresAttrSpec(TestCase): + """Presentation-preview attribute specification tests""" + + def test_posture(self): + self_attested = IndyPresAttrSpec(name="ident", cred_def_id=None, value="655321") + assert self_attested.posture == IndyPresAttrSpec.Posture.SELF_ATTESTED + + revealed = IndyPresAttrSpec( + name="ident", cred_def_id=CD_ID["score"], value="655321" + ) + assert revealed.posture == IndyPresAttrSpec.Posture.REVEALED_CLAIM + + unrevealed = IndyPresAttrSpec(name="ident", cred_def_id=CD_ID["score"]) + assert unrevealed.posture == IndyPresAttrSpec.Posture.UNREVEALED_CLAIM + + no_posture = IndyPresAttrSpec(name="no_spec") + assert no_posture.posture is None + + def test_list_plain(self): + by_list = IndyPresAttrSpec.list_plain( + plain={"ident": "655321", " Given Name ": "Alexander DeLarge"}, + cred_def_id=CD_ID["score"], + ) + explicit = [ + IndyPresAttrSpec(name="ident", cred_def_id=CD_ID["score"], value="655321"), + IndyPresAttrSpec( + name="givenname", cred_def_id=CD_ID["score"], value="Alexander DeLarge" + ), + ] + + # order could be askew + for listp in by_list: + assert any(xp == listp for xp in explicit) + assert len(explicit) == len(by_list) + + def test_list_plain_share_referent(self): + by_list = IndyPresAttrSpec.list_plain( + plain={"ident": "655321", " Given Name ": "Alexander DeLarge"}, + cred_def_id=CD_ID["score"], + referent="dummy", + ) + explicit = [ + IndyPresAttrSpec( + name="ident", + cred_def_id=CD_ID["score"], + value="655321", + referent="dummy", + ), + IndyPresAttrSpec( + name="givenname", + cred_def_id=CD_ID["score"], + value="Alexander DeLarge", + referent="dummy", + ), + ] + + # order could be askew + for listp in by_list: + assert any(xp == listp for xp in explicit) + assert len(explicit) == len(by_list) + + def test_eq(self): + attr_specs_none_plain = [ + IndyPresAttrSpec(name="name", value="value"), + IndyPresAttrSpec(name="name", value="value", mime_type=None), + IndyPresAttrSpec(name=" NAME ", value="value"), + ] + attr_specs_different = [ + IndyPresAttrSpec(name="name", value="dmFsdWU=", mime_type="image/png"), + IndyPresAttrSpec(name="name", value="value", cred_def_id="cred_def_id"), + IndyPresAttrSpec(name="name", value="distinct value", mime_type=None), + IndyPresAttrSpec(name="distinct name", value="value", mime_type=None), + IndyPresAttrSpec(name="name", value="dmFsdWU=", mime_type=None), + IndyPresAttrSpec(name="name"), + IndyPresAttrSpec( + name="name", value="value", cred_def_id="cred_def_id", referent="reft-0" + ), + IndyPresAttrSpec( + name="name", value="value", cred_def_id="cred_def_id", referent="reft-1" + ), + ] + + for lhs in attr_specs_none_plain: + for rhs in attr_specs_different: + assert lhs != rhs + + for lidx in range(len(attr_specs_none_plain) - 1): + for ridx in range(lidx + 1, len(attr_specs_none_plain)): + assert attr_specs_none_plain[lidx] == attr_specs_none_plain[ridx] + + for lidx in range(len(attr_specs_different) - 1): + for ridx in range(lidx + 1, len(attr_specs_different)): + assert attr_specs_different[lidx] != attr_specs_different[ridx] + + def test_deserialize(self): + """Test deserialization.""" + dump = json.dumps( + { + "name": "PLAYER", + "cred_def_id": CD_ID["score"], + "value": "Richie Knucklez", + } + ) + + attr_spec = IndyPresAttrSpec.deserialize(dump) + assert type(attr_spec) == IndyPresAttrSpec + assert canon(attr_spec.name) == "player" + + dump = json.dumps( + { + "name": "PLAYER", + "cred_def_id": CD_ID["score"], + "value": "Richie Knucklez", + "referent": "0", + } + ) + + attr_spec = IndyPresAttrSpec.deserialize(dump) + assert type(attr_spec) == IndyPresAttrSpec + assert canon(attr_spec.name) == "player" + + def test_serialize(self): + """Test serialization.""" + + attr_spec_dict = PRES_PREVIEW.attributes[0].serialize() + assert attr_spec_dict == { + "name": "player", + "cred_def_id": CD_ID["score"], + "value": "Richie Knucklez", + } + + attr_spec_dict = PRES_PREVIEW_ATTR_NAMES.attributes[0].serialize() + assert attr_spec_dict == { + "name": "player", + "cred_def_id": CD_ID["score"], + "value": "Richie Knucklez", + "referent": "reft-0", + } + + +class TestIndyPresPredSpec(TestCase): + """Presentation predicate specification tests""" + + def test_deserialize(self): + """Test deserialization.""" + dump = json.dumps( + { + "name": "HIGH SCORE", + "cred_def_id": CD_ID["score"], + "predicate": ">=", + "threshold": 1000000, + } + ) + + pred_spec = IndyPresPredSpec.deserialize(dump) + assert type(pred_spec) == IndyPresPredSpec + assert canon(pred_spec.name) == "highscore" + + def test_serialize(self): + """Test serialization.""" + + pred_spec_dict = PRES_PREVIEW.predicates[0].serialize() + assert pred_spec_dict == { + "name": "highScore", + "cred_def_id": CD_ID["score"], + "predicate": ">=", + "threshold": 1000000, + } + + def test_eq(self): + """Test equality operator.""" + + pred_spec_a = IndyPresPredSpec( + name="a", + cred_def_id=CD_ID["score"], + predicate=Predicate.GE.value.math, + threshold=0, + ) + pred_spec_b = IndyPresPredSpec( + name="b", + cred_def_id=CD_ID["score"], + predicate=Predicate.GE.value.math, + threshold=0, + ) + assert pred_spec_a != pred_spec_b + + pred_spec_a.name = "b" + assert pred_spec_a == pred_spec_b + + pred_spec_a.predicate = Predicate.LE.value.math + assert pred_spec_a != pred_spec_b + + pred_spec_a.predicate = Predicate.GE.value.math + assert pred_spec_a == pred_spec_b + + pred_spec_a.threshold = 100 + assert pred_spec_a != pred_spec_b + + pred_spec_a.threshold = 0 + pred_spec_a.cred_def_id = None + assert pred_spec_a != pred_spec_b + + +@pytest.mark.indy +class TestIndyPresPreviewAsync(AsyncTestCase): + """Presentation preview tests""" + + @pytest.mark.asyncio + async def test_to_indy_proof_request(self): + """Test presentation preview to indy proof request.""" + + indy_proof_req = await PRES_PREVIEW.indy_proof_request( + **{k: INDY_PROOF_REQ[k] for k in ("name", "version", "nonce")} + ) + + assert indy_proof_req == INDY_PROOF_REQ + + @pytest.mark.asyncio + async def test_to_indy_proof_request_attr_names(self): + """Test presentation preview to indy proof request.""" + + indy_proof_req = await PRES_PREVIEW_ATTR_NAMES.indy_proof_request( + **{k: INDY_PROOF_REQ_ATTR_NAMES[k] for k in ("name", "version", "nonce")} + ) + + assert indy_proof_req == INDY_PROOF_REQ_ATTR_NAMES + + async def test_to_indy_proof_request_self_attested(self): + """Test presentation preview to indy proof request with self-attested values.""" + + pres_preview_selfie = deepcopy(PRES_PREVIEW) + for attr_spec in pres_preview_selfie.attributes: + attr_spec.cred_def_id = None + + indy_proof_req_selfie = await pres_preview_selfie.indy_proof_request( + **{k: INDY_PROOF_REQ[k] for k in ("name", "version", "nonce")} + ) + + assert not any( + "restrictions" in attr_spec + for attr_spec in indy_proof_req_selfie["requested_attributes"].values() + ) + + @pytest.mark.asyncio + async def test_to_indy_proof_request_revo_default_interval(self): + """Test pres preview to indy proof req with revocation support, defaults.""" + + copy_indy_proof_req = deepcopy(INDY_PROOF_REQ) + + pres_preview = deepcopy(PRES_PREVIEW) + mock_profile = InMemoryProfile.test_profile() + context = mock_profile.context + context.injector.bind_instance( + IndyLedgerRequestsExecutor, IndyLedgerRequestsExecutor(mock_profile) + ) + with async_mock.patch.object( + IndyLedgerRequestsExecutor, "get_ledger_for_identifier" + ) as mock_get_ledger: + mock_get_ledger.return_value = ( + None, + async_mock.MagicMock( + get_credential_definition=async_mock.CoroutineMock( + return_value={"value": {"revocation": {"...": "..."}}} + ) + ), + ) + indy_proof_req_revo = await pres_preview.indy_proof_request( + **{k: INDY_PROOF_REQ[k] for k in ("name", "version", "nonce")}, + profile=mock_profile, + ) + + for uuid, attr_spec in indy_proof_req_revo["requested_attributes"].items(): + assert set(attr_spec.get("non_revoked", {}).keys()) == {"from", "to"} + copy_indy_proof_req["requested_attributes"][uuid][ + "non_revoked" + ] = attr_spec["non_revoked"] + for uuid, pred_spec in indy_proof_req_revo["requested_predicates"].items(): + assert set(pred_spec.get("non_revoked", {}).keys()) == {"from", "to"} + copy_indy_proof_req["requested_predicates"][uuid][ + "non_revoked" + ] = pred_spec["non_revoked"] + + assert copy_indy_proof_req == indy_proof_req_revo + + @pytest.mark.asyncio + async def test_to_indy_proof_request_revo(self): + """Test pres preview to indy proof req with revocation support, interval.""" + + EPOCH_NOW = int(time()) + copy_indy_proof_req = deepcopy(INDY_PROOF_REQ) + + pres_preview = deepcopy(PRES_PREVIEW) + mock_profile = InMemoryProfile.test_profile() + mock_profile.settings["ledger.ledger_config_list"] = [{"id": "test"}] + context = mock_profile.context + context.injector.bind_instance( + IndyLedgerRequestsExecutor, IndyLedgerRequestsExecutor(mock_profile) + ) + context.injector.bind_instance( + BaseMultitenantManager, + async_mock.MagicMock(MultitenantManager, autospec=True), + ) + with async_mock.patch.object( + IndyLedgerRequestsExecutor, "get_ledger_for_identifier" + ) as mock_get_ledger: + mock_get_ledger.return_value = ( + None, + async_mock.MagicMock( + get_credential_definition=async_mock.CoroutineMock( + return_value={"value": {"revocation": {"...": "..."}}} + ) + ), + ) + indy_proof_req_revo = await pres_preview.indy_proof_request( + **{k: INDY_PROOF_REQ[k] for k in ("name", "version", "nonce")}, + profile=mock_profile, + non_revoc_intervals={ + CD_ID[s_id]: IndyNonRevocationInterval(1234567890, EPOCH_NOW) + for s_id in S_ID + }, + ) + + for uuid, attr_spec in indy_proof_req_revo["requested_attributes"].items(): + assert set(attr_spec.get("non_revoked", {}).keys()) == {"from", "to"} + copy_indy_proof_req["requested_attributes"][uuid][ + "non_revoked" + ] = attr_spec["non_revoked"] + for uuid, pred_spec in indy_proof_req_revo["requested_predicates"].items(): + assert set(pred_spec.get("non_revoked", {}).keys()) == {"from", "to"} + copy_indy_proof_req["requested_predicates"][uuid][ + "non_revoked" + ] = pred_spec["non_revoked"] + + assert copy_indy_proof_req == indy_proof_req_revo + + @pytest.mark.asyncio + async def test_satisfaction(self): + """Test presentation preview predicate satisfaction.""" + + pred_spec = IndyPresPredSpec( + name="highScore", + cred_def_id=CD_ID["score"], + predicate=Predicate.GE.value.math, + threshold=1000000, + ) + attr_spec = IndyPresAttrSpec( + name="HIGHSCORE", cred_def_id=CD_ID["score"], value=1234567 + ) + assert attr_spec.satisfies(pred_spec) + + attr_spec = IndyPresAttrSpec( + name="HIGHSCORE", cred_def_id=CD_ID["score"], value=985260 + ) + assert not attr_spec.satisfies(pred_spec) + + +@pytest.mark.indy +class TestIndyPresPreview(TestCase): + """Presentation preview tests""" + + def test_init(self): + """Test initializer.""" + assert PRES_PREVIEW.attributes + assert PRES_PREVIEW.predicates + assert PRES_PREVIEW.has_attr_spec( + cred_def_id=CD_ID["score"], name="player", value="Richie Knucklez" + ) + + def test_type(self): + """Test type.""" + assert PRES_PREVIEW._type == DIDCommPrefix.qualify_current(PRESENTATION_PREVIEW) + + def test_deserialize(self): + """Test deserialization.""" + dump = { + "@type": DIDCommPrefix.qualify_current(PRESENTATION_PREVIEW), + "attributes": [ + { + "name": "player", + "cred_def_id": CD_ID["score"], + "value": "Richie Knucklez", + }, + { + "name": "screenCapture", + "cred_def_id": CD_ID["score"], + "mime-type": "image/png", + "value": "aW1hZ2luZSBhIHNjcmVlbiBjYXB0dXJl", + }, + ], + "predicates": [ + { + "name": "highScore", + "cred_def_id": CD_ID["score"], + "predicate": ">=", + "threshold": 1000000, + } + ], + } + + preview = IndyPresPreview.deserialize(dump) + assert type(preview) == IndyPresPreview + + def test_serialize(self): + """Test serialization.""" + + preview_dict = PRES_PREVIEW.serialize() + assert preview_dict == { + "@type": DIDCommPrefix.qualify_current(PRESENTATION_PREVIEW), + "attributes": [ + { + "name": "player", + "cred_def_id": CD_ID["score"], + "value": "Richie Knucklez", + }, + { + "name": "screenCapture", + "cred_def_id": CD_ID["score"], + "mime-type": "image/png", + "value": "aW1hZ2luZSBhIHNjcmVlbiBjYXB0dXJl", + }, + ], + "predicates": [ + { + "name": "highScore", + "cred_def_id": CD_ID["score"], + "predicate": ">=", + "threshold": 1000000, + } + ], + } + + def test_eq(self): + pres_preview_a = IndyPresPreview.deserialize(PRES_PREVIEW.serialize()) + assert pres_preview_a == PRES_PREVIEW + + pres_preview_a.predicates = [] + assert pres_preview_a != PRES_PREVIEW diff --git a/aries_cloudagent/indy/models/tests/test_proof.py b/aries_cloudagent/indy/models/tests/test_proof.py new file mode 100644 index 0000000000..195894798f --- /dev/null +++ b/aries_cloudagent/indy/models/tests/test_proof.py @@ -0,0 +1,213 @@ +from unittest import TestCase + +from ..proof import IndyProof, IndyPrimaryProof + +INDY_PROOF = { + "proof": { + "proofs": [ + { + "primary_proof": { + "eq_proof": { + "revealed_attrs": { + "legalname": "17452692860386304610406162367026442418073686683521403362495551314143111443652", + "sriregdate": "84794510088035165945015616272801238926353986227884872357629651520394786581106", + }, + "a_prime": "32278501255905171704742958460322893511637162983431935430586198860290226716940011148630099119208052729112758092925039150874617283183927448645371882581685088575199866941867151165491046651967119052964806526135854070644719112949280568148440903644904710068267486800869485749248349461501620728165451495601555669946384333482966245666190278766177061903214027961074019726272411064340093147955497074304630819094166118938615860755615625023078371588293961721725584452546589831014718739623649233248030194356309258161615125464664035123726423139356572242556757045992526062527483806001865085714219252862005117598919151259032522148889", + "e": "35581451552777769685033243384248421572266001898564008605825317561882904440337036643694472157139199169811306345839082839123770144966895910", + "v": "828135525411477722854522947279129529367824493967974661374467145531948008574830974054079283576780714374153012435459948298649103950012856757893702662580316994202540537968066097584782835786817798593382226962016632660559415029091269783131619661150305144161138937512000672990134231506531835018380842894907585523024600357659856760842088978208463181270540996881521313384300664824864197511211768635380758711304109184849426935800528766783989679813547280761825711586667758612892241157731649649671977796509420405204721364323414531438941036273864593005341987433237851455813509580997247737506421671498820538106999006733109420915932517313399525288279220340543074911283732812241240611988838828615608276736374076503183912535082267877825448476018260528143644362338273289271933480597163222466868740551402490479042036399483966063683273100741081681151652755476630114805470239996939074679562728379896561493489436212716612638391498204887984935", + "m": { + "jurisdictionid": "4688090583250683500407492066026666992233534897866356770488663088356350501628623200115192654864645776575059205900108991424870893822763046867291093307598457278513670200299320511463", + "master_secret": "5765990116706384142055090460230893901254240108285092077892244151401046441144630456667590155248838177142351186285450410555444057303439562237518662184356939898507515088151665055566", + }, + "m2": "2119622127109596166553032536805743625111577193704785214102429084853473314741322393298752334122295828925696657072866449946647347230484843925603741440638027", + }, + "ge_proofs": [], + }, + "non_revoc_proof": { + "x_list": { + "rho": "1381A1FA6226405CD4D778ECE06598AAE0BE53257E68A6C2B022BAB357ECF06A", + "r": "0917CA5871E4FDBB8A2B9897030D2DB42A6C0240B3A58251203773D6E4431A36", + "r_prime": "09FB5273DC4220BCB831D50F3E17E1B5BFCA7CA77479E1E055157D7EC2840218", + "r_prime_prime": "0DD2B81306168752C811BF02D8898EDDD5271B67D17C42A709579124795A39FA", + "r_prime_prime_prime": "0D3CF03C63E9BF905CB4944C5AB14C3B0B07E45AB37FCDEF08132BAB949EAC28", + "o": "0780B5E65B610F742577CFECC2BBAC029E89F85888FE4155396AFDA95248FA70", + "o_prime": "2167D381A357AD436DA6FF114736DB030F43624E69DFE163E67F95819A949A07", + "m": "20420CD1D5D1015F212466A14FB91BDBD9F193010697C7A41A561F22FFA50A73", + "m_prime": "0BCEFFDF86A7F99B2BD6FDCF55E5FC8A1AB792B573AC729E62CB9365894FC49F", + "t": "121FF75FAF8659D2FAF82A106890EE1A7D55BB97837B75DF04A6EB73E2FADC11", + "t_prime": "0D0F7FA1D8D0CDA5CEC6839462449880960EECC5D5BE2492DC784636596B15F5", + "m2": "188DF30745A95D0784171FC6C12BA0F1BB8B55AAAB0BFCAA31BE6BAC9FB000F4", + "s": "0A5FBEF4E5381FDD3AC873A4C33F0A7CA2F98723C6C277B178EB475DC7F62D2B", + "c": "058276E1EEDC087D577006BAF47D620808F05A51523683CD7574007690C8F634", + }, + "c_list": { + "e": "6 5D7859942AA7C79B39B184BF6EE34F6DB90FBF13C67CD625582A087AC829CD7E 4 14CDD3630ADC7709CDF0725797887FDBFBF461D78D49547BAE0391D08CD948D4 4 1C5E7F30EC2E00F9270F48EB1FD1ACBBB38FBC57437B92CC1222A627FD38D18B", + "d": "6 63C2A154DC37F522876D70D540F8973F139F61B7D9A45CA0ACD4111FC03466FF 4 21C7AE31847BBB8DE7CA65C42618ECD7E9316F12E1B200313D64CD1EB3886920 4 26704886831DC1F7F7BA0798464F79F46C6A71F48DB1CD2E17552E72B4A62D2C", + "a": "6 370625C7C0A9B91E90406C632A7094052B59404D939628FF430B41AB5D7631C2 4 43FE8D1FB3DDEF09BF40ED91C560B1BB64C3C7757BE645F57ED73730144E1B4D 4 23FC00129E9331CB517D12B69EBED153164D0A7B50182529D23AC2E44B7601B9", + "g": "6 477DE87612F38F4FD17972C138B29E0D6F3B19E00BF51202DF292AA5C7407B18 4 428091929CC1D5BA454AA6E08D2E6E22EA0194318F9A6F725308050EDD6EEE2B 4 345CB5EA0DE020ADA9DAB2D33A032FC43E1ED38EF783C4FBC50EE3259EBD828C", + "w": "21 10DE65930CDABB708343BC4698741CE2368AD7678D9E8E92D556617FF9F1C0F82 21 14280B106C12058E5FA8CCC384C4FCB905C0FFCFD775A9D90C8A9B5EADB5F00AF 6 7B727DF8F7763C31916E0500AA4B827D3FE7C8D09F8781EE6D1114610CEE02D2 4 08055D795A3FA8A310CE2D1D35ED4B43483DC30475A340F6AB77E60891AB88D7 6 717DF6D200998E0FC9626D2C6FAB791A9CF1060EA66F55667E1097B5F9D9651A 4 2439CCEE5AEB9018479E2F81E1ACBC97B993A5072EC0454A2C399C876544CF7D", + "s": "21 13904BB2C505D54B72C6AEAF1D963B998514D32E529BB8DF3D206EC10D8062E83 21 137621CFF97C9387F55E2261908D4261109C482BD0E0EEC2F0B659FB7C06B5B3F 6 6ADC283E13D215EE2F5FBB6D13CC4A10C433E6C34082F6649CC1475119D53FE0 4 17B097D1FE378BBB570AFB54D2D54A716AF1094A48DB25B1B8E736ECEE1C6A37 6 77BD003ACF6359795E9D5AFC8D2C2F92B5D0B1A17CC8894BD0AD80893DCC3145 4 1A23B81929CF9E9B8F06452E54544A18E96CCD208A9B57BE5C16242291D4F9A1", + "u": "21 1244078FABBEC377E653E9A9AA4D440B2629BCFC81BBE5F340F6CCAFF482C5F64 21 141638A50019F7E5C43AEAB9C08A00CA6C7A583CC9E21721637A414563AF99FB9 6 894AE47A984A73C03C35FE26002AB423D880104308CF659EBC802B428474F3D0 4 2FE5FA6F02EF8560B1A110EF469B6931C647A85CD99553FD66F19F0A84860B3D 6 6B224C57D9D733D98C790AAE59788E543F0B601E74332045D6D5A48BD2BB5E78 4 328ED59D6112D424A5F9EB1B53A99A1F1B695C201AAEED9FB5495756902DE885", + }, + }, + }, + { + "primary_proof": { + "eq_proof": { + "revealed_attrs": { + "businesslang": "33232985271439191533377453938110666726604825575392012661293897468349434596941", + "legalname": "17452692860386304610406162367026442418073686683521403362495551314143111443652", + }, + "a_prime": "22833405208527909805658627885236743099336730027825383472338187366214272320337675330990871468796305703537931344033496173776963733705008698600674612254082652713935912645330450683745452495876678273146518048789998721046334776140574708429790975677611594817588065443920567166228081407726989775436653333217881175657845993033938320108749200873171887106723038728373011874060294858910535397084640450131604883364402761538578849447322016884121681512954350064697804811600546281829928443200867026343486921907911964121739038205696088198316492988705815389566788346860454163848001286839771027707507085608676234176821335949882609151879", + "e": "26211430566728236949472934666404280381429729589602985581360422640738650344423199293303624818679368126760453576661571079887786380504909424", + "v": "1183284034125513434526626632453906974024524122355179523163126038427036805660983835291935114724299313011784006409952674194385609314327918950991009227419001790455124235810281676445556361348409120531467227183568063899352946599694953555292292880707030808509851961755055299318374725899096891634542198204148975182963583869495598072844883575645558703940563599413472042728588455433115752776418904275765036927947162756648980766277698357542965839090542945779559326248044767339087391276187923390193967764307014239203880241940667352417447173202287290314079589500282199297479084444031821076350230144839969982651461307607892468488827083453775427345643243839043737086138612353717748072909685557621989119812355961743302809042378983830233918558000415683431547466069557881763682741302771877937684330818471653540484887846945607347348992231866758380480694787988464880611417493797868733007451847225767128713573784142146719971958554655166517625", + "m": { + "sriregdate": "7575840339790901971126627968837082098626881291739401780806831297827045216866658144901928397498006110265170328305919646086754771869317780632931473346911624167001626838610534568843", + "master_secret": "5765990116706384142055090460230893901254240108285092077892244151401046441144630456667590155248838177142351186285450410555444057303439562237518662184356939898507515088151665055566", + "jurisdictionid": "14096839792687668090922075920092837277326004351667261353604206746884866410340361120653504934566708275430326153729312326004083085529265517108356327181857586189913372170459175729709", + }, + "m2": "2119622127109596166553032536805743625111577193704785214102429084853473314741325255195002226364320268187061126795878932443225594537196014952825318487949064", + }, + "ge_proofs": [ + { + "u": { + "3": "14214025934608562077646947167995470869060367847484688477838543465198741322418093474815111495367149686315148847259831278865241365209384224232954423346984854905942783706673160208640", + "2": "14023370669565473291694092130357532707361476334542375350079348190834535906680939872574620435509920568784041847935643881238594980373831048766809683781827213987859676408535849428069", + "0": "7342720149800723920049085527885265335922694657934633887893010535330060404347356228131389857905259847481527908547866960993849483727806784544271817532383560767510252739450820430297", + "1": "12930108427693598462294132989946197198385148171877437530363114996696998329204786445472597167502572414089948183256933231462127117088661956490732350007130196957027682927664007849658", + }, + "r": { + "DELTA": "2276011444312040342177562010127944407206471771850161462011654696029342185527024189779503642466540879503375664970692496221292943337880107380182197681199735646409639460310954096256041883426178537558699617219326605585528418106779400398247492051144494972428159314152305402374525943058962489706836275018074879534905118582562522427229900853927999927959921074680199095872894611297374460046245988175067781594445231402555436922926356352049679447076072906691029517188855451501709086551608514024649309244081422609087275812131326961124014262659223943820640287328415882873680732167765640462280199146540904634993199545501393181617445462059091954661868502783750376727414888895378635536610512795047161770800173743978258282815323798995", + "1": "145149166200984508754979331730271650691795059798885849413005398737646540673572796195049028576636858430003736791937280395933429790146358370300465795044845619413090172147042178825796242296189459000963168675142944905821133466353450895170166263470384194507284899124088802588181666362822241089334957725837879453009186865584587784193266176491276610018054057024541423659182491586897069427691457562403438267402863460964331448616927445294885139172523914248218566405910602793346981980875592380295812060121717266020529176064925967534742106030667826236337946961590474915610622834934925872041458052270951297851073431816315266795471496701719035598544703542900159094238036317894294440680846865411989382193271218550053346736524378090", + "3": "488242262482251066336600519024239149920945820909803682984138843905329976488753317398237110919606489927089359558153542999969293897450152653769523734260465352344018467036352374271817223858693824706082267934514106409897025269132807613593510744710198445233676405461706854538390095368608374150646804968541094830749611323284157217580822910471203232502232920449193845097941371165010773542220021680961857593548858434528770834197814112784764573149604514968309297299129422430022731158982111734276335932748703841355647540221220235245628861109415553677906418350890407336322231455024554973355128185109292909400204653100022155834264558113422742374787498432051156180056915579661389036207137633808397107069334955079021718840046475740", + "0": "1889616213417675662027875323831687679606977901203237164062977174534795712692142405694518356428677653465183695707520741824048628171958421933228302981587300939585899561556164553686639041366495357346706024068593024482876446314389439808067174943877530921644465721688762078031523444351973815942452033520624310772876299834533474980944254684225959838998330440672465002366831279275338579813986121501781838397398509803631315760442180955079109042428151699266385669936917334768811328989734199091636249060358340285444710581471460477163814472432604767781583584634327120526125679621718877544594062397774670136205301735294209068059667059035113840554190788593424842476782037779774100712520753135034354307478795855384146102846291422325", + "2": "2256303315125016834439436777492999741791160377376603220468802552504141866513037544234087214702615599485347175200313859812001977863781904875946481595504988017384259236184266088270426514521281378457507050789483814801478499595933999464167811424628487541649424258526511079312011144782002240713571565990349188794909974154062490438204229646615941281994590538687808662516031195951250068887811909625228470867024144015034321118956157816308562072476341838958442831936361331061849739039582311599006782736574367184119332638156053506860974276436399008420893534094538443949848303215619624731404149943675070922177701636329363568655502418065808003716609101369409081151890753329575485528878386404856836884965131088809939644518239521714", + }, + "mj": "14096839792687668090922075920092837277326004351667261353604206746884866410340361120653504934566708275430326153729312326004083085529265517108356327181857586189913372170459175729709", + "alpha": "39732014402544066666305752691257351436478792500315973953060895408903156945745295298481231505551106605612400455996248942179005054886204630409163784217146177530904446243078206759306905448438288136897900207969449133498955545726742733646146882695835058150428914834247244807298219323871888394264705707380276719195247948412936421225065274267698430423334359679052053736739609684520324626677167709830296370179401801362973146687673580211321931455713913794645528599190941480829778662321575539160526412685608729405724868316856673561351275842908701968672147908119404226954713721899039602551167566571471365237859825455158944264075087922347837949928215646727196175315950375225785471245890970883732544673245325805751881316942316012048005298720143305095369445267837301634518621030789511255476734017276019872535773209546564817594583596289579731197870049561", + "t": { + "DELTA": "24507807620968538169926786257196669663829628674945176548989559869833453290754619620752886336680152859587107093453995882927471852312781833873622867489011952726714264538573904721218082147870198466108957261768826812812170727337972103344693646816412861333858987157750482373613343951980503221206042917670615376266085322204366168616039760714683928191729787727342391756728901601834014076830881170568815795080648062044728985406683922625366015906135480407893500533141362120361247547598564354037737202959092025827091301849750648379238398435389516356932367622595223731492389505666326468305427861559849457127890963973993229331775", + "2": "76047176608221028468519068489304314238068927063738045321887967648199391545767995466193781936836238600070583986803314771247022831681027057050992136857533056266939849412222984164422800005541618825953123158190447019477513008894322693103132726884636591844788538677508747959148326195695369091593543905012989818594686325738432314327818921040787184442041944415711923150079925346862529052384000072885255838453428376447734488419675582259159543112005228841298206953170182265488107572659064310422482701100783064784028501120021033348231889240639718001161677021014013250819831484739561572871065868606265352688805847775729983323490", + "0": "50679642745295171923754683706358400214215498217155467843053196976352383406649124593469550219051236944021392439227799246685393814878606896980505288554698380886315461386392767072850002305225877469317997689653935634475889777757078925967337801390999183907627565604245032760367997623083771902974154252956308592766930461005004633581391988846694633161601793254249152437137771845388433159461685101301008637318307727456090126403781067470287661852382485122721922020212328871373424587102594010853318355840386896946223724584604343345399290890382974188908034054992994126975461059705330501374087167313570051240874831686860535148690", + "3": "66928337729696351875172133217609950642698331512240958210447316634445698868251665776819872201185607560694862241492988131882627133504316351013134163298788094339304327928726132791872357561275025379458234234053405489065087803384700395514204215458341958560097080133736340662119856219302296170105994082472398049039676829832979944368411772609308307954514698868126341775937530639178527097257195450374909072450223394668043707074275108199486996243536540009813015336466206178050408983489515950391026045067133630454696849861957051457866759646668437115244581106699290519193884280115629711024195319359748492628008587094999803907922", + "1": "16240527026356196137054497550786075095784627443121029704904628658834445965912560629125780050088347057023945024821470223012907572705360383627912546405536939585080156986943699251080108631292150906072925545896153910943611031783144627498463176086496637341332138643393971992105181064495710454434251712758795193299697974135090441014333990840372353673648818587434278396053075776482004040551215221410252915766622684129115640966096470571261130636873342630326061726100141042504104637584319960956869273304261208793038741148332173066892414396252690977873698574706269672026291285492456514196769163274660188030603118109409575042683", + }, + "predicate": { + "attr_name": "jurisdictionid", + "p_type": "GE", + "value": 1, + }, + } + ], + }, + "non_revoc_proof": { + "x_list": { + "rho": "1F4D25232DBDD4396751A11CCDF58EB92BD3D88C7A9EEA397B31032763FA612A", + "r": "1995EA715D057F29E9DF0C00BB94D8E7B3300B89D4AE0AE5FDBE9CC18ACA3C00", + "r_prime": "11A611A0C0CF8179C0E454BFE9BD4023FA75EFBE723EF4831EE2B819C8284436", + "r_prime_prime": "0C02A6F3A338F0BAECA78B8BAB2F51A18E2E66A1DBEF44305F4B9E986304F039", + "r_prime_prime_prime": "194CB95AC8837B5AF27A480BB2677F710C166ACBC76125DBD0972F5D656D708C", + "o": "242A0D9550CA19819A4731BADC67A95EB57390E5F63177EB37C777C2D194B857", + "o_prime": "0AD930637E1DBABC1A072F00382C64946384025CE423403BDC4B87F1D74C71AE", + "m": "24DC7C3E16FEBD6C0F1AE0BA9BCEA64CDDA97CD9C6BBE668371A36136A92BBA1", + "m_prime": "2061BF9B182EFECFB26463938BA24C7D2D7367A7F961185EA8AA48B7027F703B", + "t": "0A2622F19A15442FCAF3DD5760B1E72312E81FFFDF29348E2B155C6AD5C3314F", + "t_prime": "20B3423B69FB4292C3F25C4F2916FCC55F79BAC25FB2E86781705FAA2543151F", + "m2": "1EE1B9A6FFD96A061631208FDC0FCFB92B3FCAE9F5E2D68EA9FC9F4ED5FD83B1", + "s": "071EBD7E181313D80511D26357DFDA68EFE61FF1E49A197CDDA238BFD919FBF1", + "c": "035CC19E347CFF534C05E894BB8132A5E5E734B370724D241D6D3AAA3D0BB1E0", + }, + "c_list": { + "e": "6 4ED73798B056F7C18552BD7941052CB983CE664B2E6C4D1FF9C54027D99E0990 4 199AEBA72D16AB760E4F59A39118EB37D7C277A203B9A101EA5E2B6665A0934F 4 2E9DC7AF3BEBD094DC2BBB35EE8DA903876B32AEA2B6FF912290FBB3EEDE29F9", + "d": "6 5FCF702955B8D291668F9CE390974551EA5C4C1005F2B51308405539A6EECBFD 4 3B7CA4CAE9CADAFD50EA6086F46D087AA21D8FDCF63642D69FB698B837457649 4 22FF97065821FAEE7EB8C1E43B729286ADFBC3737E9AE6EF8293375ABFDE3419", + "a": "6 3AE88157825BC0E106C5CF3C5A93F5A502E22ADEB022D807AF6E2DEC96F6782E 4 0F4C6F27766296E42416F34375BC40C2EB85402DE6A7B7D2AAFF70F37842E6DC 4 0B065C82FF0E7E14DEFC3CA1E11AB41F0FE3C7A42D29FBB0825B370490601590", + "g": "6 333B3C68202913B6504F95CA75456E9183177BECDA2664777E3169DAFBC91800 4 23F577FBD5751BC0DC3BE1453758B69ADB2BF7F1F8C2BFB7AC10A7829A97F849 4 0D01A6FC5442DAE3D9960D1846DDA5FE87463B06970840549A517574BECA7D6B", + "w": "21 1264C72BBBB93BA1C9E33EEA06B5B7430AE1AD92FEC33AFD31806A3D36EB8D7D4 21 1315520B29BBA556225A889DD94139CF09CBD4AEA52E25F4D2261C96EBC9C387A 6 544948B78028D965166AB0D72EE4F42F5BBFDE696734D2742914A95B4CFE1292 4 1E679EFE7019FFFB99853EF4A7302CD9FCD0A4A4D600E7A3EEE3462D1932E706 6 7DA3017F7ADD0CD6BC5C58706EF39F8F982E35ECA342E6E5CD8D2F9C9C0447C4 4 29DE1EEE64DBD075FFF48C97B94E54543556BEB9A571FFA81F8F40ABC4B1E092", + "s": "21 1303A150FE2A205C2E5C41B79FE0FC6BECA41E01395850E09AE2607728A4C325D 21 110BD3B3458D3E9726813C079D41867F5387D6C719E6E8DE8CD6F54AB922233F9 6 755AE2489963E7EA28DE09E84416388F14AD54F854FD948F7317572435BA812E 4 188994E6AF6F0B02E47F1E5D2028BD0ECF12A566BD91B790331DD56CC6BEB3C8 6 5BB8902625C93CA36CFD6BFF797C5C0A91E45B036A7BC82311F2CF045BF6E8CB 4 1ABD443497615B36A6E783BCFF373706E4AE87A0CFAB2C73F9483F8089D2F835", + "u": "21 12022CB134154E2750D0B3953FA64E94FB7A80077E40E9FA642F81CA6B1E008F9 21 1176C5A5F3C69FC1BF64DCF6EEC66972F07D5552B773913BF1CF79D03806837B4 6 7F62D3DE296AE18601CA7C0E771C00B1ABFE6247B6547275D08962B29B4D19E1 4 42A100ED48BDF5FA9CD54170AF96B10E76C734A89358BB56680924E232BB6D19 6 5CE5233C06C0A912554CE7C40D8413023481CDDF8DF68EBEB8F27F104661D741 4 033EC06C452DA00A4458E58D2F9D7FEE5394039F98F55B336A09E59CFEAA574B", + }, + }, + }, + ], + "aggregated_proof": { + "c_hash": "64284694157010500037871584756977982783162678320180085811668324501813312008648", + "c_list": [[67, 46, 45, 63]], + }, + }, + "requested_proof": { + "revealed_attrs": { + "20_legalname_uuid": { + "sub_proof_index": 0, + "raw": "Tart City", + "encoded": "17452692860386304610406162367026442418073686683521403362495551314143111443652", + }, + "21_businesslang_uuid": { + "sub_proof_index": 1, + "raw": "EN-CA", + "encoded": "33232985271439191533377453938110666726604825575392012661293897468349434596941", + }, + "20_sriregdate_uuid": { + "sub_proof_index": 0, + "raw": "2019-04-11", + "encoded": "84794510088035165945015616272801238926353986227884872357629651520394786581106", + }, + "21_legalname_uuid": { + "sub_proof_index": 1, + "raw": "Tart City", + "encoded": "17452692860386304610406162367026442418073686683521403362495551314143111443652", + }, + }, + "revealed_attr_groups": { # this part is a graft to exercise attr groups + "18_0_uuid": { + "sub_proof_index": 0, + "values": { + "endDate": { + "raw": "", + "encoded": "102987336249554097029535212322581322789799900648198034993379397001115665086549", + }, + "id": {"raw": "3", "encoded": "3"}, + "effectiveDate": { + "raw": "2012-12-01", + "encoded": "58785836675119218543950531421539993546216494060018521243314445986885543138388", + }, + "jurisdictionId": {"raw": "1", "encoded": "1"}, + "legalName": { + "raw": "Tart City", + "encoded": "17452692860386304610406162367026442418073686683521403362495551314143111443652", + }, + "orgTypeId": {"raw": "2", "encoded": "2"}, + "busId": {"raw": "11144444", "encoded": "11144444"}, + }, + } + }, + "self_attested_attrs": {}, + "unrevealed_attrs": {}, + "predicates": {"21_jurisdictionid_GE_uuid": {"sub_proof_index": 1}}, + }, + "identifiers": [ + { + "schema_id": "WgWxqztrNooG92RXvxSTWv:2:sri:1.0", + "cred_def_id": "WgWxqztrNooG92RXvxSTWv:3:CL:20:tag", + "rev_reg_id": "WgWxqztrNooG92RXvxSTWv:4:WgWxqztrNooG92RXvxSTWv:3:CL:20:tag:CL_ACCUM:0", + "timestamp": 1554990827, + }, + { + "schema_id": "WgWxqztrNooG92RXvxSTWv:2:sri:1.1", + "cred_def_id": "WgWxqztrNooG92RXvxSTWv:3:CL:21:tag", + "rev_reg_id": "WgWxqztrNooG92RXvxSTWv:4:WgWxqztrNooG92RXvxSTWv:3:CL:21:tag:CL_ACCUM:0", + "timestamp": 1554990827, + }, + ], +} + + +class TestIndyProof(TestCase): + """Test indy proof marshmallow integration.""" + + def test_serde(self): + """Test de/serialization.""" + proof = IndyProof.deserialize(INDY_PROOF) + assert type(proof) == IndyProof + + proof_dict = proof.serialize() + assert proof_dict == INDY_PROOF diff --git a/aries_cloudagent/indy/models/tests/test_proof_request.py b/aries_cloudagent/indy/models/tests/test_proof_request.py new file mode 100644 index 0000000000..a6bb8503c4 --- /dev/null +++ b/aries_cloudagent/indy/models/tests/test_proof_request.py @@ -0,0 +1,89 @@ +from unittest import TestCase + +from ..non_rev_interval import IndyNonRevocationInterval +from ..proof_request import IndyProofRequest + +TEST_DID = "LjgpST2rjsoxYegQDRm7EL" +SCHEMA_NAME = "preferences" +SCHEMA_TXN = 12 +SCHEMA_ID = f"{TEST_DID}:2:{SCHEMA_NAME}:1.0" +CRED_DEF_ID = f"{TEST_DID}:3:CL:12:default" +REV_REG_ID = f"{TEST_DID}:4:{CRED_DEF_ID}:CL_ACCUM:0" + +PROOF_REQ = { + "nonce": "123432421212", + "name": "proof_req_1", + "version": "0.1", + "requested_attributes": { + "attr1_referent": { + "name": "name", + "restrictions": [ + { + "schema_id": SCHEMA_ID, + "schema_issuer_did": TEST_DID, + "schema_name": SCHEMA_NAME, + "schema_version": "1.0", + "issuer_did": TEST_DID, + "cred_def_id": CRED_DEF_ID, + } + ], + "non_revoked": { # overrides proof-level spec + "from": 1234567890, + "to": 1234567890, + }, + } + }, + "requested_predicates": { + "predicate1_referent": { + "name": "age", + "p_type": ">=", + "p_value": 18, + "restrictions": [ + { + "schema_id": SCHEMA_ID, + "schema_issuer_did": TEST_DID, + "schema_name": SCHEMA_NAME, + "schema_version": "1.0", + "issuer_did": TEST_DID, + "cred_def_id": CRED_DEF_ID, + } + ], + "non_revoked": { # overrides proof-level spec + "from": 1234567890, + "to": 1234567890, + }, + } + }, + "non_revoked": {"from": 1584704048, "to": 1584704048}, +} + + +class TestIndyProofReq(TestCase): + """Test indy proof req.""" + + def test_serde(self): + """Test de/serialization.""" + + proof_req = IndyProofRequest.deserialize(PROOF_REQ) + assert type(proof_req) == IndyProofRequest + + ser = proof_req.serialize() + assert ser == PROOF_REQ + + deser = IndyProofRequest.deserialize(ser) + reser = deser.serialize() + assert ser == reser + + obj = IndyProofRequest(**PROOF_REQ) + ser = obj.serialize() + assert ser == reser + + obj2 = IndyProofRequest( + nonce=obj.nonce, + name=obj.name, + version=obj.version, + requested_attributes=obj.requested_attributes, + requested_predicates=obj.requested_predicates, + non_revoked=obj.non_revoked, + ) + assert ser.items() <= obj2.serialize().items() diff --git a/aries_cloudagent/indy/models/xform.py b/aries_cloudagent/indy/models/xform.py new file mode 100644 index 0000000000..afcb635fe2 --- /dev/null +++ b/aries_cloudagent/indy/models/xform.py @@ -0,0 +1,130 @@ +"""Utilities to deal with indy.""" + +from ...indy.holder import IndyHolder + +from .pres_preview import IndyPresPreview + + +async def indy_proof_req_preview2indy_requested_creds( + indy_proof_req: dict, + preview: IndyPresPreview = None, + *, + holder: IndyHolder, +): + """ + Build indy requested-credentials structure. + + Given input proof request and presentation preview, use credentials in + holder's wallet to build indy requested credentials structure for input + to proof creation. + + Args: + indy_proof_req: indy proof request + pres_preview: preview from presentation proposal, if applicable + holder: holder injected into current context + + """ + req_creds = { + "self_attested_attributes": {}, + "requested_attributes": {}, + "requested_predicates": {}, + } + + for referent, req_item in indy_proof_req["requested_attributes"].items(): + credentials = await holder.get_credentials_for_presentation_request_by_referent( + presentation_request=indy_proof_req, + referents=(referent,), + start=0, + count=100, + ) + if not credentials: + raise ValueError( + "Could not automatically construct presentation for " + + f"presentation request {indy_proof_req['name']}" + + f":{indy_proof_req['version']} because referent " + + f"{referent} did not produce any credentials." + ) + + # match returned creds against any preview values + if len(credentials) == 1: + cred_match = credentials[0] + elif preview: + reft = indy_proof_req["requested_attributes"][referent] + names = [reft["name"]] if "name" in reft else reft.get("names") + for cred in credentials: # holder sorts by irrevocability, least referent + if all( + preview.has_attr_spec( + cred_def_id=cred["cred_info"]["cred_def_id"], + name=name, + value=cred["cred_info"]["attrs"][name], + ) + for name in names + ): + cred_match = cred + break + else: + raise ValueError( + "Could not automatically construct presentation for " + + f"presentation request {indy_proof_req['name']}" + + f":{indy_proof_req['version']} because referent " + + f"{referent} did not produce any credentials matching " + + "proposed preview." + ) + else: + cred_match = credentials[0] # holder sorts + + if "restrictions" in indy_proof_req["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" + ][indy_proof_req["requested_attributes"][referent]["name"]] + + for referent in indy_proof_req["requested_predicates"]: + credentials = await holder.get_credentials_for_presentation_request_by_referent( + presentation_request=indy_proof_req, + referents=(referent,), + start=0, + count=100, + ) + if not credentials: + raise ValueError( + "Could not automatically construct presentation for " + + f"presentation request {indy_proof_req['name']}" + + f":{indy_proof_req['version']} because predicate " + + f"referent {referent} did not produce any credentials." + ) + + cred_match = credentials[0] # holder sorts + if "restrictions" in indy_proof_req["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" + ][indy_proof_req["requested_predicates"][referent]["name"]] + + return req_creds + + +def indy_proof_req2non_revoc_intervals(indy_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 indy_proof_req[req_item_type].items(): + interval = req_item.get( + "non_revoked", + indy_proof_req.get("non_revoked"), + ) + if interval: + fro = interval.get("from") + to = interval.get("to") + if (to is not None) and fro == to: + interval["from"] = 0 # accommodate indy-sdk verify=False if fro=to + non_revoc_intervals[reft] = interval + return non_revoc_intervals diff --git a/aries_cloudagent/indy/sdk/__init__.py b/aries_cloudagent/indy/sdk/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aries_cloudagent/indy/sdk/error.py b/aries_cloudagent/indy/sdk/error.py new file mode 100644 index 0000000000..a79e0a6194 --- /dev/null +++ b/aries_cloudagent/indy/sdk/error.py @@ -0,0 +1,43 @@ +"""Indy error handling.""" + +from typing import Type + +from indy.error import IndyError + +from ...core.error import BaseError + + +class IndyErrorHandler: + """Trap IndyError and raise an appropriate LedgerError instead.""" + + def __init__(self, message: str = None, error_cls: Type[BaseError] = BaseError): + """Init the context manager.""" + self.error_cls = error_cls + self.message = message + + def __enter__(self): + """Enter the context manager.""" + return self + + def __exit__(self, err_type, err_value, err_traceback): + """Exit the context manager.""" + if isinstance(err_value, IndyError): + raise IndyErrorHandler.wrap_error( + err_value, self.message, self.error_cls + ) from err_value + + @classmethod + def wrap_error( + cls, + err_value: IndyError, + message: str = None, + error_cls: Type[BaseError] = BaseError, + ) -> BaseError: + """Create an instance of BaseError from an IndyError.""" + err_msg = message or "Exception while performing indy operation" + indy_message = hasattr(err_value, "message") and err_value.message + if indy_message: + err_msg += f": {indy_message}" + err = error_cls(err_msg) + err.__traceback__ = err_value.__traceback__ + return err diff --git a/aries_cloudagent/indy/sdk/holder.py b/aries_cloudagent/indy/sdk/holder.py new file mode 100644 index 0000000000..48cb2abac0 --- /dev/null +++ b/aries_cloudagent/indy/sdk/holder.py @@ -0,0 +1,483 @@ +"""Indy SDK holder implementation.""" + +import json +import logging +import re + +from collections import OrderedDict +from typing import Sequence, Tuple, Union + +import indy.anoncreds +from indy.error import ErrorCode, IndyError + +from ...indy.sdk.wallet_setup import IndyOpenWallet +from ...ledger.base import BaseLedger +from ...storage.indy import IndySdkStorage +from ...storage.error import StorageError, StorageNotFoundError +from ...storage.record import StorageRecord +from ...wallet.error import WalletNotFoundError + +from ..holder import IndyHolder, IndyHolderError + +from .error import IndyErrorHandler +from .util import create_tails_reader + +LOGGER = logging.getLogger(__name__) + + +class IndySdkHolder(IndyHolder): + """Indy-SDK holder implementation.""" + + def __init__(self, wallet: IndyOpenWallet): + """ + Initialize an IndyHolder instance. + + Args: + wallet: IndyOpenWallet instance + + """ + self.wallet = wallet + + async def create_credential_request( + self, credential_offer: dict, credential_definition: dict, holder_did: str + ) -> Tuple[str, str]: + """ + Create a credential request for the given credential offer. + + Args: + credential_offer: The credential offer to create request for + credential_definition: The credential definition to create an offer for + holder_did: the DID of the agent making the request + + Returns: + A tuple of the credential request and credential request metadata + + """ + + with IndyErrorHandler( + "Error when creating credential request", IndyHolderError + ): + ( + credential_request_json, + credential_request_metadata_json, + ) = await indy.anoncreds.prover_create_credential_req( + self.wallet.handle, + holder_did, + json.dumps(credential_offer), + json.dumps(credential_definition), + self.wallet.master_secret_id, + ) + + LOGGER.debug( + "Created credential request. " + "credential_request_json=%s credential_request_metadata_json=%s", + credential_request_json, + credential_request_metadata_json, + ) + + return credential_request_json, credential_request_metadata_json + + async def store_credential( + self, + credential_definition: dict, + credential_data: dict, + credential_request_metadata: dict, + credential_attr_mime_types=None, + credential_id: str = None, + rev_reg_def: dict = None, + ) -> str: + """ + Store a credential in the wallet. + + Args: + credential_definition: Credential definition for this credential + credential_data: Credential data generated by the issuer + credential_request_metadata: credential request metadata generated + by the issuer + credential_attr_mime_types: dict mapping attribute names to (optional) + MIME types to store as non-secret record, if specified + credential_id: optionally override the stored credential id + rev_reg_def: revocation registry definition in json + + Returns: + the ID of the stored credential + + """ + with IndyErrorHandler( + "Error when storing credential in wallet", IndyHolderError + ): + credential_id = await indy.anoncreds.prover_store_credential( + wallet_handle=self.wallet.handle, + cred_id=credential_id, + cred_req_metadata_json=json.dumps(credential_request_metadata), + cred_json=json.dumps(credential_data), + cred_def_json=json.dumps(credential_definition), + rev_reg_def_json=json.dumps(rev_reg_def) if rev_reg_def else None, + ) + + if credential_attr_mime_types: + mime_types = { + attr: credential_attr_mime_types.get(attr) + for attr in credential_data["values"] + if attr in credential_attr_mime_types + } + if mime_types: + record = StorageRecord( + type=IndyHolder.RECORD_TYPE_MIME_TYPES, + value=credential_id, + tags=mime_types, + id=f"{IndyHolder.RECORD_TYPE_MIME_TYPES}::{credential_id}", + ) + indy_stor = IndySdkStorage(self.wallet) + await indy_stor.add_record(record) + + return credential_id + + async def get_credentials(self, start: int, count: int, wql: dict): + """ + Get credentials stored in the wallet. + + Args: + start: Starting index + count: Number of records to return + wql: wql query dict + + """ + + async def fetch(limit): + """Fetch up to limit (default smaller of all remaining or 256) creds.""" + creds = [] + CHUNK = min(record_count, limit or record_count, IndyHolder.CHUNK) + cardinality = min(limit or record_count, record_count) + + with IndyErrorHandler( + "Error fetching credentials from wallet", IndyHolderError + ): + while len(creds) < cardinality: + batch = json.loads( + await indy.anoncreds.prover_fetch_credentials( + search_handle, CHUNK + ) + ) + creds.extend(batch) + if len(batch) < CHUNK: + break + return creds + + with IndyErrorHandler( + "Error when constructing wallet credential query", IndyHolderError + ): + ( + search_handle, + record_count, + ) = await indy.anoncreds.prover_search_credentials( + self.wallet.handle, json.dumps(wql) + ) + + if start > 0: + # must move database cursor manually + await fetch(start) + credentials = await fetch(count) + + await indy.anoncreds.prover_close_credentials_search(search_handle) + + return credentials + + async def get_credentials_for_presentation_request_by_referent( + self, + presentation_request: dict, + referents: Sequence[str], + start: int, + count: int, + extra_query: dict = {}, + ): + """ + Get credentials stored in the wallet. + + Args: + presentation_request: Valid presentation request from issuer + referents: Presentation request referents to use to search for creds + start: Starting index + count: Maximum number of records to return + extra_query: wql query dict + + """ + + async def fetch(reft, limit): + """Fetch up to limit (default smaller of all remaining or 256) creds.""" + creds = [] + CHUNK = min(IndyHolder.CHUNK, limit or IndyHolder.CHUNK) + + with IndyErrorHandler( + "Error fetching credentials from wallet for presentation request", + IndyHolderError, + ): + while not limit or len(creds) < limit: + batch = json.loads( + await indy.anoncreds.prover_fetch_credentials_for_proof_req( + search_handle, reft, CHUNK + ) + ) + creds.extend(batch) + if len(batch) < CHUNK: + break + return creds + + with IndyErrorHandler( + "Error when constructing wallet credential query", IndyHolderError + ): + search_handle = await ( + indy.anoncreds.prover_search_credentials_for_proof_req( + self.wallet.handle, + json.dumps(presentation_request), + json.dumps(extra_query), + ) + ) + + if not referents: + referents = ( + *presentation_request["requested_attributes"], + *presentation_request["requested_predicates"], + ) + creds_dict = OrderedDict() + + try: + for reft in referents: + # must move database cursor manually + if start > 0: + await fetch(reft, start) + credentials = await fetch(reft, count) + + for cred in credentials: + cred_id = cred["cred_info"]["referent"] + if cred_id not in creds_dict: + cred["presentation_referents"] = {reft} + creds_dict[cred_id] = cred + else: + creds_dict[cred_id]["presentation_referents"].add(reft) + finally: + # Always close + await indy.anoncreds.prover_close_credentials_search_for_proof_req( + search_handle + ) + + for cred in creds_dict.values(): + cred["presentation_referents"] = list(cred["presentation_referents"]) + + creds_ordered = tuple( + [ + cred + for cred in sorted( + creds_dict.values(), + key=lambda c: ( + c["cred_info"]["rev_reg_id"] or "", # irrevocable 1st + c["cred_info"][ + "referent" + ], # should be descending by timestamp if we had it + ), + ) + ] + )[:count] + return creds_ordered + + async def get_credential(self, credential_id: str) -> str: + """ + Get a credential stored in the wallet. + + Args: + credential_id: Credential id to retrieve + + """ + try: + credential_json = await indy.anoncreds.prover_get_credential( + self.wallet.handle, credential_id + ) + except IndyError as err: + if err.error_code == ErrorCode.WalletItemNotFound: + raise WalletNotFoundError( + "Credential {} not found in wallet {}".format( + credential_id, self.wallet.name + ) + ) + else: + raise IndyErrorHandler.wrap_error( + err, + f"Error when fetching credential {credential_id}", + IndyHolderError, + ) from err + + return credential_json + + async def credential_revoked( + self, ledger: BaseLedger, credential_id: str, fro: int = None, to: int = None + ) -> bool: + """ + Check ledger for revocation status of credential by cred id. + + Args: + credential_id: Credential id to check + + """ + cred = json.loads(await self.get_credential(credential_id)) + rev_reg_id = cred["rev_reg_id"] + + if rev_reg_id: + cred_rev_id = int(cred["cred_rev_id"]) + (rev_reg_delta, _) = await ledger.get_revoc_reg_delta( + rev_reg_id, + fro, + to, + ) + + return cred_rev_id in rev_reg_delta["value"].get("revoked", []) + else: + return False + + async def delete_credential(self, credential_id: str): + """ + Remove a credential stored in the wallet. + + Args: + credential_id: Credential id to remove + + """ + try: + indy_stor = IndySdkStorage(self.wallet) + mime_types_record = await indy_stor.get_record( + IndyHolder.RECORD_TYPE_MIME_TYPES, + f"{IndyHolder.RECORD_TYPE_MIME_TYPES}::{credential_id}", + ) + await indy_stor.delete_record(mime_types_record) + except StorageNotFoundError: + pass # MIME types record not present: carry on + + try: + await indy.anoncreds.prover_delete_credential( + self.wallet.handle, credential_id + ) + except IndyError as err: + if err.error_code == ErrorCode.WalletItemNotFound: + raise WalletNotFoundError( + "Credential {} not found in wallet {}".format( + credential_id, self.wallet.name + ) + ) + else: + raise IndyErrorHandler.wrap_error( + err, "Error when deleting credential", IndyHolderError + ) from err + + async def get_mime_type( + self, credential_id: str, attr: str = None + ) -> Union[dict, str]: + """ + Get MIME type per attribute (or for all attributes). + + Args: + credential_id: credential id + attr: attribute of interest or omit for all + + Returns: Attribute MIME type or dict mapping attribute names to MIME types + attr_meta_json = all_meta.tags.get(attr) + + """ + try: + mime_types_record = await IndySdkStorage(self.wallet).get_record( + IndyHolder.RECORD_TYPE_MIME_TYPES, + f"{IndyHolder.RECORD_TYPE_MIME_TYPES}::{credential_id}", + ) + except StorageError: + return None # no MIME types: not an error + + return mime_types_record.tags.get(attr) if attr else mime_types_record.tags + + async def create_presentation( + self, + presentation_request: dict, + requested_credentials: dict, + schemas: dict, + credential_definitions: dict, + rev_states: dict = None, + ) -> str: + """ + Get credentials stored in the wallet. + + Args: + presentation_request: Valid indy format presentation request + requested_credentials: Indy format requested credentials + schemas: Indy formatted schemas JSON + credential_definitions: Indy formatted credential definitions JSON + rev_states: Indy format revocation states JSON + + """ + + for reft, spec in presentation_request.get("requested_attributes", {}).items(): + for r in spec.get("restrictions", []): + for k in r: + m = re.match("^attr::(.*)::value$", k) + if not m: + continue + + named_attrs = ( + [spec["name"]] if "name" in spec else spec.get("names", []) + ) + restricted_attr = m.group(1) + if m and restricted_attr not in named_attrs: # wrong attr: hopeless + LOGGER.error( + f"Presentation request {presentation_request['nonce']} " + f"requested attribute {reft} names {named_attrs} " + f"but restricts {restricted_attr} value" + ) + raise IndyHolderError( + f"Requested attribute {reft} names {named_attrs} " + f"but restricts {restricted_attr} value" + ) + + with IndyErrorHandler("Error when constructing proof", IndyHolderError): + presentation_json = await indy.anoncreds.prover_create_proof( + self.wallet.handle, + json.dumps(presentation_request), + json.dumps(requested_credentials), + self.wallet.master_secret_id, + json.dumps(schemas), + json.dumps(credential_definitions), + json.dumps(rev_states) if rev_states else "{}", + ) + + return presentation_json + + async def create_revocation_state( + self, + cred_rev_id: str, + rev_reg_def: dict, + rev_reg_delta: dict, + timestamp: int, + tails_file_path: str, + ) -> str: + """ + Create current revocation state for a received credential. + + Args: + cred_rev_id: credential revocation id in revocation registry + rev_reg_def: revocation registry definition + rev_reg_delta: revocation delta + timestamp: delta timestamp + + Returns: + the revocation state + + """ + + with IndyErrorHandler( + "Error when constructing revocation state", IndyHolderError + ): + tails_file_reader = await create_tails_reader(tails_file_path) + rev_state_json = await indy.anoncreds.create_revocation_state( + tails_file_reader, + rev_reg_def_json=json.dumps(rev_reg_def), + cred_rev_id=cred_rev_id, + rev_reg_delta_json=json.dumps(rev_reg_delta), + timestamp=timestamp, + ) + + return rev_state_json diff --git a/aries_cloudagent/indy/sdk/issuer.py b/aries_cloudagent/indy/sdk/issuer.py new file mode 100644 index 0000000000..3d84473c93 --- /dev/null +++ b/aries_cloudagent/indy/sdk/issuer.py @@ -0,0 +1,385 @@ +"""Indy SDK issuer implementation.""" + +import json +import logging +from typing import Sequence, Tuple + +import indy.anoncreds +import indy.blob_storage +from indy.error import AnoncredsRevocationRegistryFullError, IndyError, ErrorCode + +from ...indy.sdk.profile import IndySdkProfile +from ...messaging.util import encode +from ...storage.error import StorageError + +from ..issuer import ( + IndyIssuer, + IndyIssuerError, + IndyIssuerRevocationRegistryFullError, + DEFAULT_CRED_DEF_TAG, + DEFAULT_SIGNATURE_TYPE, +) + +from .error import IndyErrorHandler +from .util import create_tails_reader, create_tails_writer + +LOGGER = logging.getLogger(__name__) + + +class IndySdkIssuer(IndyIssuer): + """Indy-SDK issuer implementation.""" + + def __init__(self, profile: IndySdkProfile): + """ + Initialize an IndyIssuer instance. + + Args: + profile: IndySdkProfile instance + + """ + self.profile = profile + + async def create_schema( + self, + origin_did: str, + schema_name: str, + schema_version: str, + attribute_names: Sequence[str], + ) -> Tuple[str, str]: + """ + Create a new credential schema. + + Args: + origin_did: the DID issuing the credential definition + schema_name: the schema name + schema_version: the schema version + attribute_names: a sequence of schema attribute names + + Returns: + A tuple of the schema ID and JSON + + """ + + with IndyErrorHandler("Error when creating schema", IndyIssuerError): + schema_id, schema_json = await indy.anoncreds.issuer_create_schema( + origin_did, + schema_name, + schema_version, + json.dumps(attribute_names), + ) + return (schema_id, schema_json) + + async def credential_definition_in_wallet( + self, credential_definition_id: str + ) -> bool: + """ + Check whether a given credential definition ID is present in the wallet. + + Args: + credential_definition_id: The credential definition ID to check + """ + try: + await indy.anoncreds.issuer_create_credential_offer( + self.profile.wallet.handle, credential_definition_id + ) + return True + except IndyError as err: + if err.error_code not in ( + ErrorCode.CommonInvalidStructure, + ErrorCode.WalletItemNotFound, + ): + raise IndyErrorHandler.wrap_error( + err, + "Error when checking wallet for credential definition", + IndyIssuerError, + ) from err + # recognized error signifies no such cred def in wallet: pass + return False + + async def create_and_store_credential_definition( + self, + origin_did: str, + schema: dict, + signature_type: str = None, + tag: str = None, + support_revocation: bool = False, + ) -> Tuple[str, str]: + """ + Create a new credential definition and store it in the wallet. + + Args: + origin_did: the DID issuing the credential definition + schema: the schema used as a basis + signature_type: the credential definition signature type (default 'CL') + tag: the credential definition tag + support_revocation: whether to enable revocation for this credential def + + Returns: + A tuple of the credential definition ID and JSON + + """ + + with IndyErrorHandler( + "Error when creating credential definition", IndyIssuerError + ): + ( + credential_definition_id, + credential_definition_json, + ) = await indy.anoncreds.issuer_create_and_store_credential_def( + self.profile.wallet.handle, + origin_did, + json.dumps(schema), + tag or DEFAULT_CRED_DEF_TAG, + signature_type or DEFAULT_SIGNATURE_TYPE, + json.dumps({"support_revocation": support_revocation}), + ) + return (credential_definition_id, credential_definition_json) + + async def create_credential_offer(self, credential_definition_id: str) -> str: + """ + Create a credential offer for the given credential definition id. + + Args: + credential_definition_id: The credential definition to create an offer for + + Returns: + The created credential offer + + """ + with IndyErrorHandler( + "Exception when creating credential offer", IndyIssuerError + ): + credential_offer_json = await indy.anoncreds.issuer_create_credential_offer( + self.profile.wallet.handle, credential_definition_id + ) + + return credential_offer_json + + async def create_credential( + self, + schema: dict, + credential_offer: dict, + credential_request: dict, + credential_values: dict, + rev_reg_id: str = None, + tails_file_path: str = None, + ) -> Tuple[str, str]: + """ + Create a credential. + + Args + schema: Schema to create credential for + credential_offer: Credential Offer to create credential for + credential_request: Credential request to create credential for + credential_values: Values to go in credential + rev_reg_id: ID of the revocation registry + tails_file_path: Path to the local tails file + + Returns: + A tuple of created credential and revocation id + + """ + + encoded_values = {} + schema_attributes = schema["attrNames"] + for attribute in schema_attributes: + # Ensure every attribute present in schema to be set. + # Extraneous attribute names are ignored. + try: + credential_value = credential_values[attribute] + except KeyError: + raise IndyIssuerError( + "Provided credential values are missing a value " + + f"for the schema attribute '{attribute}'" + ) + + encoded_values[attribute] = {} + encoded_values[attribute]["raw"] = str(credential_value) + encoded_values[attribute]["encoded"] = encode(credential_value) + + tails_reader_handle = ( + await create_tails_reader(tails_file_path) + if tails_file_path is not None + else None + ) + + try: + ( + credential_json, + cred_rev_id, + _, # rev_reg_delta_json only for ISSUANCE_ON_DEMAND, excluded by design + ) = await indy.anoncreds.issuer_create_credential( + self.profile.wallet.handle, + json.dumps(credential_offer), + json.dumps(credential_request), + json.dumps(encoded_values), + rev_reg_id, + tails_reader_handle, + ) + except AnoncredsRevocationRegistryFullError: + LOGGER.warning( + "Revocation registry %s is full: cannot create credential", + rev_reg_id, + ) + raise IndyIssuerRevocationRegistryFullError( + f"Revocation registry {rev_reg_id} is full" + ) + except IndyError as err: + raise IndyErrorHandler.wrap_error( + err, "Error when issuing credential", IndyIssuerError + ) from err + except StorageError as err: + LOGGER.warning( + ( + "Created issuer cred rev record for " + "Could not store issuer cred rev record for " + "rev reg id %s, cred rev id %s: %s" + ), + rev_reg_id, + cred_rev_id, + err.roll_up, + ) + + return (credential_json, cred_rev_id) + + async def revoke_credentials( + self, + rev_reg_id: str, + tails_file_path: str, + cred_rev_ids: Sequence[str], + ) -> Tuple[str, Sequence[str]]: + """ + Revoke a set of credentials in a revocation registry. + + Args: + rev_reg_id: ID of the revocation registry + tails_file_path: path to the local tails file + cred_rev_ids: sequences of credential indexes in the revocation registry + + Returns: + Tuple with the combined revocation delta, list of cred rev ids not revoked + + """ + failed_crids = set() + tails_reader_handle = await create_tails_reader(tails_file_path) + + result_json = None + for cred_rev_id in set(cred_rev_ids): + with IndyErrorHandler( + "Exception when revoking credential", IndyIssuerError + ): + try: + delta_json = await indy.anoncreds.issuer_revoke_credential( + self.profile.wallet.handle, + tails_reader_handle, + rev_reg_id, + cred_rev_id, + ) + except IndyError as err: + if err.error_code == ErrorCode.AnoncredsInvalidUserRevocId: + LOGGER.error( + ( + "Abstaining from revoking credential on " + "rev reg id %s, cred rev id=%s: " + "already revoked or not yet issued" + ), + rev_reg_id, + cred_rev_id, + ) + else: + LOGGER.error( + IndyErrorHandler.wrap_error( + err, "Revocation error", IndyIssuerError + ).roll_up + ) + failed_crids.add(int(cred_rev_id)) + continue + except StorageError as err: + LOGGER.warning( + ( + "Revoked credential on rev reg id %s, cred rev id %s " + "without corresponding issuer cred rev record: %s" + ), + rev_reg_id, + cred_rev_id, + err.roll_up, + ) + # carry on with delta merge; record is best-effort + + if result_json: + result_json = await self.merge_revocation_registry_deltas( + result_json, delta_json + ) + else: + result_json = delta_json + + return (result_json, [str(rev_id) for rev_id in sorted(failed_crids)]) + + async def merge_revocation_registry_deltas( + self, fro_delta: str, to_delta: str + ) -> str: + """ + Merge revocation registry deltas. + + Args: + fro_delta: original delta in JSON format + to_delta: incoming delta in JSON format + + Returns: + Merged delta in JSON format + + """ + + return await indy.anoncreds.issuer_merge_revocation_registry_deltas( + fro_delta, to_delta + ) + + async def create_and_store_revocation_registry( + self, + origin_did: str, + cred_def_id: str, + revoc_def_type: str, + tag: str, + max_cred_num: int, + tails_base_path: str, + ) -> Tuple[str, str, str]: + """ + Create a new revocation registry and store it in the wallet. + + Args: + origin_did: the DID issuing the revocation registry + cred_def_id: the identifier of the related credential definition + revoc_def_type: the revocation registry type (default CL_ACCUM) + tag: the unique revocation registry tag + max_cred_num: the number of credentials supported in the registry + tails_base_path: where to store the tails file + + Returns: + A tuple of the revocation registry ID, JSON, and entry JSON + + """ + + tails_writer = await create_tails_writer(tails_base_path) + + with IndyErrorHandler( + "Exception when creating revocation registry", IndyIssuerError + ): + ( + rev_reg_id, + rev_reg_def_json, + rev_reg_entry_json, + ) = await indy.anoncreds.issuer_create_and_store_revoc_reg( + self.profile.wallet.handle, + origin_did, + revoc_def_type, + tag, + cred_def_id, + json.dumps( + { + "issuance_type": "ISSUANCE_BY_DEFAULT", + "max_cred_num": max_cred_num, + } + ), + tails_writer, + ) + return (rev_reg_id, rev_reg_def_json, rev_reg_entry_json) diff --git a/aries_cloudagent/indy/sdk/profile.py b/aries_cloudagent/indy/sdk/profile.py new file mode 100644 index 0000000000..525e52917b --- /dev/null +++ b/aries_cloudagent/indy/sdk/profile.py @@ -0,0 +1,196 @@ +"""Manage Indy-SDK profile interaction.""" + +import asyncio +import logging + +from typing import Any, Mapping +from weakref import finalize, ref + +from ...config.injection_context import InjectionContext +from ...config.provider import ClassProvider +from ...core.profile import Profile, ProfileManager, ProfileSession +from ...core.error import ProfileError +from ...ledger.base import BaseLedger +from ...ledger.indy import IndySdkLedger, IndySdkLedgerPool +from ...storage.base import BaseStorage, BaseStorageSearch +from ...storage.vc_holder.base import VCHolder +from ...wallet.base import BaseWallet +from ...wallet.indy import IndySdkWallet + +from ..holder import IndyHolder +from ..issuer import IndyIssuer +from ..verifier import IndyVerifier + +from .wallet_setup import IndyWalletConfig, IndyOpenWallet + +LOGGER = logging.getLogger(__name__) + + +class IndySdkProfile(Profile): + """Provide access to Indy profile interaction methods.""" + + BACKEND_NAME = "indy" + + def __init__( + self, + opened: IndyOpenWallet, + context: InjectionContext = None, + ): + """Create a new IndyProfile instance.""" + super().__init__(context=context, name=opened.name, created=opened.created) + self.opened = opened + self.ledger_pool: IndySdkLedgerPool = None + self.init_ledger_pool() + self.bind_providers() + self._finalizer = self._make_finalizer(opened) + + @property + def name(self) -> str: + """Accessor for the profile name.""" + return self.opened.name + + @property + def wallet(self) -> IndyOpenWallet: + """Accessor for the opened wallet instance.""" + return self.opened + + def init_ledger_pool(self): + """Initialize the ledger pool.""" + if self.settings.get("ledger.disabled"): + LOGGER.info("Ledger support is disabled") + return + + if self.settings.get("ledger.genesis_transactions"): + self.ledger_pool = self.context.inject(IndySdkLedgerPool, self.settings) + + def bind_providers(self): + """Initialize the profile-level instance providers.""" + injector = self._context.injector + + injector.bind_provider( + BaseStorageSearch, + ClassProvider("aries_cloudagent.storage.indy.IndySdkStorage", self.opened), + ) + + injector.bind_provider( + IndyHolder, + ClassProvider( + "aries_cloudagent.indy.sdk.holder.IndySdkHolder", self.opened + ), + ) + injector.bind_provider( + IndyIssuer, + ClassProvider("aries_cloudagent.indy.sdk.issuer.IndySdkIssuer", ref(self)), + ) + + injector.bind_provider( + VCHolder, + ClassProvider( + "aries_cloudagent.storage.vc_holder.indy.IndySdkVCHolder", self.opened + ), + ) + + if self.ledger_pool: + injector.bind_provider( + BaseLedger, ClassProvider(IndySdkLedger, self.ledger_pool, ref(self)) + ) + if self.ledger_pool or self.settings.get("ledger.ledger_config_list"): + injector.bind_provider( + IndyVerifier, + ClassProvider( + "aries_cloudagent.indy.sdk.verifier.IndySdkVerifier", + ref(self), + ), + ) + + def session(self, context: InjectionContext = None) -> "ProfileSession": + """Start a new interactive session with no transaction support requested.""" + return IndySdkProfileSession(self, context=context) + + def transaction(self, context: InjectionContext = None) -> "ProfileSession": + """ + Start a new interactive session with commit and rollback support. + + If the current backend does not support transactions, then commit + and rollback operations of the session will not have any effect. + """ + return IndySdkProfileSession(self, context=context) + + async def close(self): + """Close the profile instance.""" + if self.opened: + await self.opened.close() + self.opened = None + + def _make_finalizer(self, opened: IndyOpenWallet) -> finalize: + """Return a finalizer for this profile. + + See docs for weakref.finalize for more details on behavior of finalizers. + """ + + async def _closer(opened: IndyOpenWallet): + try: + await opened.close() + except Exception: + LOGGER.exception("Failed to close wallet from finalizer") + + def _finalize(opened: IndyOpenWallet): + LOGGER.debug("Profile finalizer called; closing wallet") + asyncio.get_event_loop().create_task(_closer(opened)) + + return finalize(self, _finalize, opened) + + async def remove(self): + """Remove the profile associated with this instance.""" + if not self.opened: + raise ProfileError("Wallet must be opened to remove profile") + + self.opened.config.auto_remove = True + await self.close() + + +class IndySdkProfileSession(ProfileSession): + """An active connection to the profile management backend.""" + + def __init__( + self, + profile: Profile, + *, + context: InjectionContext = None, + settings: Mapping[str, Any] = None + ): + """Create a new IndySdkProfileSession instance.""" + super().__init__(profile=profile, context=context, settings=settings) + + async def _setup(self): + """Create the session or transaction connection, if needed.""" + injector = self._context.injector + injector.bind_provider( + BaseWallet, ClassProvider(IndySdkWallet, self.profile.opened) + ) + injector.bind_provider( + BaseStorage, + ClassProvider( + "aries_cloudagent.storage.indy.IndySdkStorage", self.profile.opened + ), + ) + + +class IndySdkProfileManager(ProfileManager): + """Manager for Indy-SDK wallets.""" + + async def provision( + self, context: InjectionContext, config: Mapping[str, Any] = None + ) -> Profile: + """Provision a new instance of a profile.""" + indy_config = IndyWalletConfig(config) + opened = await indy_config.create_wallet() + return IndySdkProfile(opened, context) + + async def open( + self, context: InjectionContext, config: Mapping[str, Any] = None + ) -> Profile: + """Open an instance of an existing profile.""" + indy_config = IndyWalletConfig(config) + opened = await indy_config.open_wallet() + return IndySdkProfile(opened, context) diff --git a/aries_cloudagent/indy/sdk/tests/__init__.py b/aries_cloudagent/indy/sdk/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aries_cloudagent/indy/sdk/tests/test_holder.py b/aries_cloudagent/indy/sdk/tests/test_holder.py new file mode 100644 index 0000000000..c67cfd375c --- /dev/null +++ b/aries_cloudagent/indy/sdk/tests/test_holder.py @@ -0,0 +1,604 @@ +import json +import pytest + +from asynctest import mock as async_mock, TestCase as AsyncTestCase + +import indy.anoncreds + +from indy.error import IndyError, ErrorCode + +from ...holder import IndyHolder, IndyHolderError + +from .. import holder as test_module + + +@pytest.mark.indy +class TestIndySdkHolder(AsyncTestCase): + def setUp(self): + mock_ledger = async_mock.MagicMock( + get_credential_definition=async_mock.MagicMock(return_value={"value": {}}), + get_revoc_reg_delta=async_mock.CoroutineMock( + return_value=( + {"value": {"...": "..."}}, + 1234567890, + ) + ), + ) + mock_ledger.__aenter__ = async_mock.CoroutineMock(return_value=mock_ledger) + self.ledger = mock_ledger + self.wallet = async_mock.MagicMock() + + self.holder = test_module.IndySdkHolder(self.wallet) + assert "IndySdkHolder" in str(self.holder) + + @async_mock.patch("indy.anoncreds.prover_create_credential_req") + async def test_create_credential_request(self, mock_create_credential_req): + mock_create_credential_req.return_value = ("{}", "[]") + + cred_req_json, cred_req_meta_json = await self.holder.create_credential_request( + "credential_offer", "credential_definition", "did" + ) + + mock_create_credential_req.assert_called_once_with( + self.wallet.handle, + "did", + json.dumps("credential_offer"), + json.dumps("credential_definition"), + self.wallet.master_secret_id, + ) + + assert (json.loads(cred_req_json), json.loads(cred_req_meta_json)) == ({}, []) + + @async_mock.patch("indy.anoncreds.prover_store_credential") + async def test_store_credential(self, mock_store_cred): + mock_store_cred.return_value = "cred_id" + + cred_id = await self.holder.store_credential( + "credential_definition", "credential_data", "credential_request_metadata" + ) + + mock_store_cred.assert_called_once_with( + wallet_handle=self.wallet.handle, + cred_id=None, + cred_req_metadata_json=json.dumps("credential_request_metadata"), + cred_json=json.dumps("credential_data"), + cred_def_json=json.dumps("credential_definition"), + rev_reg_def_json=None, + ) + + assert cred_id == "cred_id" + + @async_mock.patch("indy.anoncreds.prover_store_credential") + async def test_store_credential_with_mime_types(self, mock_store_cred): + with async_mock.patch.object( + test_module, "IndySdkStorage", async_mock.MagicMock() + ) as mock_storage: + mock_storage.return_value = async_mock.MagicMock( + add_record=async_mock.CoroutineMock() + ) + + mock_store_cred.return_value = "cred_id" + + CRED_DATA = {"values": {"cameo": "d29yZCB1cA=="}} + cred_id = await self.holder.store_credential( + "credential_definition", + CRED_DATA, + "credential_request_metadata", + {"cameo": "image/png"}, + ) + + mock_store_cred.assert_called_once_with( + wallet_handle=self.wallet.handle, + cred_id=None, + cred_req_metadata_json=json.dumps("credential_request_metadata"), + cred_json=json.dumps(CRED_DATA), + cred_def_json=json.dumps("credential_definition"), + rev_reg_def_json=None, + ) + mock_storage.return_value.add_record.assert_called_once() + + assert cred_id == "cred_id" + + @async_mock.patch("indy.non_secrets.get_wallet_record") + async def test_get_credential_attrs_mime_types(self, mock_nonsec_get_wallet_record): + cred_id = "credential_id" + dummy_tags = {"a": "1", "b": "2"} + dummy_rec = { + "type": IndyHolder.RECORD_TYPE_MIME_TYPES, + "id": cred_id, + "value": "value", + "tags": dummy_tags, + } + mock_nonsec_get_wallet_record.return_value = json.dumps(dummy_rec) + + mime_types = await self.holder.get_mime_type(cred_id) + + mock_nonsec_get_wallet_record.assert_called_once_with( + self.wallet.handle, + dummy_rec["type"], + f"{IndyHolder.RECORD_TYPE_MIME_TYPES}::{dummy_rec['id']}", + json.dumps( + {"retrieveType": False, "retrieveValue": True, "retrieveTags": True} + ), + ) + + assert mime_types == dummy_tags + + @async_mock.patch("indy.non_secrets.get_wallet_record") + async def test_get_credential_attr_mime_type(self, mock_nonsec_get_wallet_record): + cred_id = "credential_id" + dummy_tags = {"a": "1", "b": "2"} + dummy_rec = { + "type": IndyHolder.RECORD_TYPE_MIME_TYPES, + "id": cred_id, + "value": "value", + "tags": dummy_tags, + } + mock_nonsec_get_wallet_record.return_value = json.dumps(dummy_rec) + + a_mime_type = await self.holder.get_mime_type(cred_id, "a") + + mock_nonsec_get_wallet_record.assert_called_once_with( + self.wallet.handle, + dummy_rec["type"], + f"{IndyHolder.RECORD_TYPE_MIME_TYPES}::{dummy_rec['id']}", + json.dumps( + {"retrieveType": False, "retrieveValue": True, "retrieveTags": True} + ), + ) + + assert a_mime_type == dummy_tags["a"] + + @async_mock.patch("indy.non_secrets.get_wallet_record") + async def test_get_credential_attr_mime_type_x(self, mock_nonsec_get_wallet_record): + cred_id = "credential_id" + dummy_tags = {"a": "1", "b": "2"} + dummy_rec = { + "type": IndyHolder.RECORD_TYPE_MIME_TYPES, + "id": cred_id, + "value": "value", + "tags": dummy_tags, + } + mock_nonsec_get_wallet_record.side_effect = test_module.StorageError() + + assert await self.holder.get_mime_type(cred_id, "a") is None + + @async_mock.patch("indy.anoncreds.prover_search_credentials") + @async_mock.patch("indy.anoncreds.prover_fetch_credentials") + @async_mock.patch("indy.anoncreds.prover_close_credentials_search") + async def test_get_credentials( + self, mock_close_cred_search, mock_fetch_credentials, mock_search_credentials + ): + SIZE = 300 + mock_search_credentials.return_value = ("search_handle", 350) + mock_fetch_credentials.side_effect = [ + json.dumps([0] * test_module.IndySdkHolder.CHUNK), + json.dumps([1] * (SIZE % test_module.IndySdkHolder.CHUNK)), + ] + + credentials = await self.holder.get_credentials(0, SIZE, {}) + mock_search_credentials.assert_called_once_with( + self.wallet.handle, json.dumps({}) + ) + + assert mock_fetch_credentials.call_count == 2 + mock_close_cred_search.assert_called_once_with("search_handle") + + assert len(credentials) == SIZE + + mock_fetch_credentials.side_effect = [ + json.dumps([0] * test_module.IndySdkHolder.CHUNK), + json.dumps([1] * (SIZE % test_module.IndySdkHolder.CHUNK)), + ] + credentials = await self.holder.get_credentials(0, 0, {}) # 0 defaults to all + assert len(credentials) == SIZE + + @async_mock.patch("indy.anoncreds.prover_search_credentials") + @async_mock.patch("indy.anoncreds.prover_fetch_credentials") + @async_mock.patch("indy.anoncreds.prover_close_credentials_search") + async def test_get_credentials_seek( + self, mock_close_cred_search, mock_fetch_credentials, mock_search_credentials + ): + mock_search_credentials.return_value = ("search_handle", 3) + mock_fetch_credentials.return_value = "[1,2,3]" + + credentials = await self.holder.get_credentials(2, 3, {}) + assert mock_fetch_credentials.call_args_list == [ + (("search_handle", 2),), + (("search_handle", 3),), + ] + + @async_mock.patch("indy.anoncreds.prover_search_credentials_for_proof_req") + @async_mock.patch("indy.anoncreds.prover_fetch_credentials_for_proof_req") + @async_mock.patch("indy.anoncreds.prover_close_credentials_search_for_proof_req") + async def test_get_credentials_for_presentation_request_by_reft( + self, + mock_prover_close_credentials_search_for_proof_req, + mock_prover_fetch_credentials_for_proof_req, + mock_prover_search_credentials_for_proof_req, + ): + SIZE = 300 + SKIP = 50 + mock_prover_search_credentials_for_proof_req.return_value = "search_handle" + mock_prover_fetch_credentials_for_proof_req.side_effect = [ + json.dumps( + [ + {"cred_info": {"referent": f"skip-{i}", "rev_reg_id": None}} + for i in range(SKIP) + ] + ), + json.dumps( + [ + { + "cred_info": { + "referent": f"reft-{i}", + "rev_reg_id": None if i % 2 else "dummy-rrid", + } + } + for i in range(test_module.IndyHolder.CHUNK) + ] + ), + json.dumps( + [ + { + "cred_info": { + "referent": f"reft-{test_module.IndyHolder.CHUNK + i}", + "rev_reg_id": None, + } + } + for i in range(SIZE % test_module.IndyHolder.CHUNK) + ] + ), + ] + + PROOF_REQ = { + "requested_attributes": {"attr_0_uuid": {"...": "..."}}, + "requested_predicates": {"pred_0_uuid": {"...": "..."}}, + } + credentials = ( + await self.holder.get_credentials_for_presentation_request_by_referent( + PROOF_REQ, + ("asdb",), + 50, + SIZE, + {"extra": "query"}, + ) + ) + + mock_prover_search_credentials_for_proof_req.assert_called_once_with( + self.wallet.handle, + json.dumps(PROOF_REQ), + json.dumps({"extra": "query"}), + ) + + assert mock_prover_fetch_credentials_for_proof_req.call_count == 3 + mock_prover_close_credentials_search_for_proof_req.assert_called_once_with( + "search_handle" + ) + + assert len(credentials) == SIZE + assert all( + not c["cred_info"]["rev_reg_id"] + for c in credentials[ + 0 : len(credentials) - (test_module.IndyHolder.CHUNK // 2) + ] + ) # irrevocable first + assert all( + c["cred_info"]["rev_reg_id"] + for c in credentials[-test_module.IndyHolder.CHUNK // 2 :] + ) # revocable last + + @async_mock.patch("indy.anoncreds.prover_search_credentials_for_proof_req") + @async_mock.patch("indy.anoncreds.prover_fetch_credentials_for_proof_req") + @async_mock.patch("indy.anoncreds.prover_close_credentials_search_for_proof_req") + async def test_get_credentials_for_presentation_request_by_referent_default_refts( + self, + mock_prover_close_credentials_search_for_proof_req, + mock_prover_fetch_credentials_for_proof_req, + mock_prover_search_credentials_for_proof_req, + ): + mock_prover_search_credentials_for_proof_req.return_value = "search_handle" + mock_prover_fetch_credentials_for_proof_req.return_value = json.dumps( + [{"cred_info": {"referent": "asdb", "rev_reg_id": None}}] + ) + + PRES_REQ = { + "requested_attributes": { + "0_a_uuid": {"...": "..."}, + "1_b_uuid": {"...": "..."}, + }, + "requested_predicates": {"2_c_ge_80": {"...": "..."}}, + } + + credentials = ( + await self.holder.get_credentials_for_presentation_request_by_referent( + PRES_REQ, + None, + 2, + 3, + ) + ) + + mock_prover_search_credentials_for_proof_req.assert_called_once_with( + self.wallet.handle, json.dumps(PRES_REQ), json.dumps({}) + ) + + @async_mock.patch("indy.anoncreds.prover_get_credential") + async def test_get_credential(self, mock_get_cred): + mock_get_cred.return_value = "{}" + credential_json = await self.holder.get_credential("credential_id") + mock_get_cred.assert_called_once_with(self.wallet.handle, "credential_id") + + assert json.loads(credential_json) == {} + + @async_mock.patch("indy.anoncreds.prover_get_credential") + async def test_get_credential_not_found(self, mock_get_cred): + mock_get_cred.side_effect = IndyError(error_code=ErrorCode.WalletItemNotFound) + with self.assertRaises(test_module.WalletNotFoundError): + await self.holder.get_credential("credential_id") + + @async_mock.patch("indy.anoncreds.prover_get_credential") + async def test_get_credential_x(self, mock_get_cred): + mock_get_cred.side_effect = IndyError("unexpected failure") + + with self.assertRaises(test_module.IndyHolderError): + await self.holder.get_credential("credential_id") + + async def test_credential_revoked(self): + with async_mock.patch.object( # no creds revoked + self.holder, "get_credential", async_mock.CoroutineMock() + ) as mock_get_cred: + mock_get_cred.return_value = json.dumps( + { + "rev_reg_id": "dummy-rrid", + "cred_rev_id": "123", + "...": "...", + } + ) + result = await self.holder.credential_revoked(self.ledger, "credential_id") + assert not result + + with async_mock.patch.object( # cred not revocable + self.holder, "get_credential", async_mock.CoroutineMock() + ) as mock_get_cred: + mock_get_cred.return_value = json.dumps( + { + "rev_reg_id": None, + "cred_rev_id": None, + "...": "...", + } + ) + result = await self.holder.credential_revoked(self.ledger, "credential_id") + assert not result + + self.ledger.get_revoc_reg_delta = async_mock.CoroutineMock( + return_value=( + { + "value": { + "revoked": [1, 2, 3], + "...": "...", + } + }, + 1234567890, + ) + ) + with async_mock.patch.object( # cred not revoked + self.holder, "get_credential", async_mock.CoroutineMock() + ) as mock_get_cred: + mock_get_cred.return_value = json.dumps( + { + "rev_reg_id": "dummy-rrid", + "cred_rev_id": "123", + "...": "...", + } + ) + result = await self.holder.credential_revoked(self.ledger, "credential_id") + assert not result + + with async_mock.patch.object( # cred revoked + self.holder, "get_credential", async_mock.CoroutineMock() + ) as mock_get_cred: + mock_get_cred.return_value = json.dumps( + { + "rev_reg_id": "dummy-rrid", + "cred_rev_id": "2", + "...": "...", + } + ) + result = await self.holder.credential_revoked(self.ledger, "credential_id") + assert result + + @async_mock.patch("indy.anoncreds.prover_delete_credential") + @async_mock.patch("indy.non_secrets.get_wallet_record") + @async_mock.patch("indy.non_secrets.delete_wallet_record") + async def test_delete_credential( + self, + mock_nonsec_del_wallet_record, + mock_nonsec_get_wallet_record, + mock_prover_del_cred, + ): + mock_nonsec_get_wallet_record.return_value = json.dumps( + { + "type": "typ", + "id": "ident", + "value": "value", + "tags": {"a": json.dumps("1"), "b": json.dumps("2")}, + } + ) + + credential = await self.holder.delete_credential("credential_id") + + mock_prover_del_cred.assert_called_once_with( + self.wallet.handle, "credential_id" + ) + + @async_mock.patch("indy.anoncreds.prover_delete_credential") + @async_mock.patch("indy.non_secrets.get_wallet_record") + @async_mock.patch("indy.non_secrets.delete_wallet_record") + async def test_delete_credential_x( + self, + mock_nonsec_del_wallet_record, + mock_nonsec_get_wallet_record, + mock_prover_del_cred, + ): + mock_nonsec_get_wallet_record.side_effect = test_module.StorageNotFoundError() + mock_prover_del_cred.side_effect = IndyError( + error_code=ErrorCode.WalletItemNotFound + ) + + with self.assertRaises(test_module.WalletNotFoundError): + await self.holder.delete_credential("credential_id") + mock_prover_del_cred.assert_called_once_with( + self.wallet.handle, "credential_id" + ) + + mock_prover_del_cred.side_effect = IndyError( + error_code=ErrorCode.CommonInvalidParam1 + ) + with self.assertRaises(test_module.IndyHolderError): + await self.holder.delete_credential("credential_id") + assert mock_prover_del_cred.call_count == 2 + + @async_mock.patch("indy.anoncreds.prover_create_proof") + async def test_create_presentation(self, mock_create_proof): + mock_create_proof.return_value = "{}" + PROOF_REQ = { + "nonce": "1554990836", + "name": "proof_req", + "version": "0.0", + "requested_attributes": { + "20_legalname_uuid": { + "name": "legalName", + "restrictions": [ + {"cred_def_id": "WgWxqztrNooG92RXvxSTWv:3:CL:20:tag"} + ], + } + }, + "requested_predicates": { + "21_jurisdictionid_GE_uuid": { + "name": "jurisdictionId", + "p_type": ">=", + "p_value": 1, + "restrictions": [ + {"cred_def_id": "WgWxqztrNooG92RXvxSTWv:3:CL:21:tag"} + ], + } + }, + } + + presentation_json = await self.holder.create_presentation( + PROOF_REQ, + "requested_credentials", + "schemas", + "credential_definitions", + ) + + mock_create_proof.assert_called_once_with( + self.wallet.handle, + json.dumps(PROOF_REQ), + json.dumps("requested_credentials"), + self.wallet.master_secret_id, + json.dumps("schemas"), + json.dumps("credential_definitions"), + json.dumps({}), + ) + + assert json.loads(presentation_json) == {} + + async def test_create_presentation_restr_attr_mismatch_x(self): + PROOF_REQS = [ + { + "nonce": "1554990836", + "name": "proof_req", + "version": "0.0", + "requested_attributes": { + "20_legalname_uuid": { + "name": "legalName", + "restrictions": [ + { + "cred_def_id": "WgWxqztrNooG92RXvxSTWv:3:CL:20:tag", + "attr::wrong::value": "Waffle Asteroid", + } + ], + } + }, + "requested_predicates": { + "21_jurisdictionid_GE_uuid": { + "name": "jurisdictionId", + "p_type": ">=", + "p_value": 1, + "restrictions": [ + {"cred_def_id": "WgWxqztrNooG92RXvxSTWv:3:CL:21:tag"} + ], + } + }, + }, + { + "nonce": "1554990836", + "name": "proof_req", + "version": "0.0", + "requested_attributes": { + "20_legalname_uuid": { + "names": ["legalName", "businessLang"], + "restrictions": [ + { + "cred_def_id": "WgWxqztrNooG92RXvxSTWv:3:CL:20:tag", + "attr::wrong::value": "Waffle Asteroid", + } + ], + } + }, + "requested_predicates": { + "21_jurisdictionid_GE_uuid": { + "name": "jurisdictionId", + "p_type": ">=", + "p_value": 1, + "restrictions": [ + {"cred_def_id": "WgWxqztrNooG92RXvxSTWv:3:CL:21:tag"} + ], + } + }, + }, + ] + + for proof_req in PROOF_REQS: + with self.assertRaises(IndyHolderError): + await self.holder.create_presentation( + proof_req, + "requested_credentials", + "schemas", + "credential_definitions", + ) + + async def test_create_revocation_state(self): + rr_state = { + "witness": {"omega": "1 ..."}, + "rev_reg": {"accum": "21 ..."}, + "timestamp": 1234567890, + } + + with async_mock.patch.object( + test_module, "create_tails_reader", async_mock.CoroutineMock() + ) as mock_create_tails_reader, async_mock.patch.object( + indy.anoncreds, "create_revocation_state", async_mock.CoroutineMock() + ) as mock_create_rr_state: + mock_create_rr_state.return_value = json.dumps(rr_state) + + cred_rev_id = "1" + rev_reg_def = {"def": 1} + rev_reg_delta = {"delta": 1} + timestamp = 1234567890 + tails_path = "/tmp/some.tails" + + result = await self.holder.create_revocation_state( + cred_rev_id, rev_reg_def, rev_reg_delta, timestamp, tails_path + ) + assert json.loads(result) == rr_state + + mock_create_rr_state.assert_awaited_once_with( + mock_create_tails_reader.return_value, + rev_reg_def_json=json.dumps(rev_reg_def), + cred_rev_id=cred_rev_id, + rev_reg_delta_json=json.dumps(rev_reg_delta), + timestamp=timestamp, + ) diff --git a/aries_cloudagent/indy/sdk/tests/test_issuer.py b/aries_cloudagent/indy/sdk/tests/test_issuer.py new file mode 100644 index 0000000000..3b007e788e --- /dev/null +++ b/aries_cloudagent/indy/sdk/tests/test_issuer.py @@ -0,0 +1,391 @@ +import json +import pytest + +from asynctest import mock as async_mock, TestCase as AsyncTestCase + +from indy.error import ( + AnoncredsRevocationRegistryFullError, + ErrorCode, + IndyError, + WalletItemNotFound, +) + +from ....config.injection_context import InjectionContext +from ....indy.sdk.profile import IndySdkProfile +from ....indy.sdk.wallet_setup import IndyWalletConfig +from ....wallet.indy import IndySdkWallet +from ....ledger.indy import IndySdkLedgerPool + +from ...issuer import IndyIssuerRevocationRegistryFullError + +from .. import issuer as test_module + + +TEST_DID = "55GkHamhTU1ZbTbV2ab9DE" +SCHEMA_NAME = "resident" +SCHEMA_VERSION = "1.0" +SCHEMA_TXN = 1234 +SCHEMA_ID = f"{TEST_DID}:2:{SCHEMA_NAME}:{SCHEMA_VERSION}" +CRED_DEF_ID = f"{TEST_DID}:3:CL:{SCHEMA_TXN}:default" +REV_REG_ID = f"{TEST_DID}:4:{CRED_DEF_ID}:CL_ACCUM:0" +TEST_RR_DELTA = { + "ver": "1.0", + "value": {"prevAccum": "1 ...", "accum": "21 ...", "issued": [1, 2, 12, 42]}, +} + + +@pytest.mark.indy +class TestIndySdkIssuer(AsyncTestCase): + async def setUp(self): + self.context = InjectionContext() + self.context.injector.bind_instance( + IndySdkLedgerPool, IndySdkLedgerPool("name") + ) + + self.wallet = await IndyWalletConfig( + { + "auto_recreate": True, + "auto_remove": True, + "key": await IndySdkWallet.generate_wallet_key(), + "key_derivation_method": "RAW", + "name": "test-wallet", + } + ).create_wallet() + with async_mock.patch.object(IndySdkProfile, "_make_finalizer"): + self.profile = IndySdkProfile(self.wallet, self.context) + self.issuer = test_module.IndySdkIssuer(self.profile) + + async def tearDown(self): + await self.profile.close() + + async def test_repr(self): + assert "IndySdkIssuer" in str(self.issuer) # cover __repr__ + + @async_mock.patch("indy.anoncreds.issuer_create_and_store_credential_def") + async def test_schema_cred_def(self, mock_indy_cred_def): + assert ( + self.issuer.make_schema_id(TEST_DID, SCHEMA_NAME, SCHEMA_VERSION) + == SCHEMA_ID + ) + + (s_id, schema_json) = await self.issuer.create_schema( + TEST_DID, + SCHEMA_NAME, + SCHEMA_VERSION, + ["name", "moniker", "genre", "effective"], + ) + assert s_id == SCHEMA_ID + schema = json.loads(schema_json) + schema["seqNo"] = SCHEMA_TXN + + assert ( + self.issuer.make_credential_definition_id(TEST_DID, schema, tag="default") + == CRED_DEF_ID + ) + + mock_indy_cred_def.return_value = ( + CRED_DEF_ID, + json.dumps({"dummy": "cred-def"}), + ) + assert (CRED_DEF_ID, json.dumps({"dummy": "cred-def"})) == ( + await self.issuer.create_and_store_credential_definition( + TEST_DID, schema, support_revocation=True + ) + ) + + @async_mock.patch("indy.anoncreds.issuer_create_credential_offer") + async def test_credential_definition_in_wallet(self, mock_indy_create_offer): + mock_indy_create_offer.return_value = {"sample": "offer"} + assert await self.issuer.credential_definition_in_wallet(CRED_DEF_ID) + + @async_mock.patch("indy.anoncreds.issuer_create_credential_offer") + async def test_credential_definition_in_wallet_no(self, mock_indy_create_offer): + mock_indy_create_offer.side_effect = WalletItemNotFound( + error_code=ErrorCode.WalletItemNotFound + ) + assert not await self.issuer.credential_definition_in_wallet(CRED_DEF_ID) + + @async_mock.patch("indy.anoncreds.issuer_create_credential_offer") + async def test_credential_definition_in_wallet_x(self, mock_indy_create_offer): + mock_indy_create_offer.side_effect = IndyError( + error_code=ErrorCode.WalletInvalidHandle + ) + with self.assertRaises(test_module.IndyIssuerError): + await self.issuer.credential_definition_in_wallet(CRED_DEF_ID) + + @async_mock.patch("indy.anoncreds.issuer_create_credential_offer") + async def test_create_credential_offer(self, mock_create_offer): + test_offer = {"test": "offer"} + test_cred_def_id = "test-cred-def-id" + mock_create_offer.return_value = json.dumps(test_offer) + mock_profile = async_mock.MagicMock() + issuer = test_module.IndySdkIssuer(mock_profile) + offer_json = await issuer.create_credential_offer(test_cred_def_id) + assert json.loads(offer_json) == test_offer + mock_create_offer.assert_called_once_with( + mock_profile.wallet.handle, test_cred_def_id + ) + + @async_mock.patch("indy.anoncreds.issuer_create_credential") + @async_mock.patch.object(test_module, "create_tails_reader", autospec=True) + @async_mock.patch("indy.anoncreds.issuer_revoke_credential") + @async_mock.patch("indy.anoncreds.issuer_merge_revocation_registry_deltas") + async def test_create_revoke_credentials( + self, + mock_indy_merge_rr_deltas, + mock_indy_revoke_credential, + mock_tails_reader, + mock_indy_create_credential, + ): + test_schema = {"attrNames": ["attr1"]} + test_offer = { + "schema_id": SCHEMA_ID, + "cred_def_id": CRED_DEF_ID, + "key_correctness_proof": {"c": "...", "xz_cap": "...", "xr_cap": ["..."]}, + "nonce": "...", + } + test_request = {"test": "request"} + test_values = {"attr1": "value1"} + test_cred = { + "schema_id": SCHEMA_ID, + "cred_def_id": CRED_DEF_ID, + "rev_reg_id": REV_REG_ID, + "values": {"attr1": {"raw": "value1", "encoded": "123456123899216581404"}}, + "signature": {"...": "..."}, + "signature_correctness_proof": {"...": "..."}, + "rev_reg": {"accum": "21 12E8..."}, + "witness": {"omega": "21 1369..."}, + } + test_cred_rev_ids = ["42", "54"] + test_rr_delta = TEST_RR_DELTA + mock_indy_create_credential.side_effect = [ + ( + json.dumps(test_cred), + cr_id, + test_rr_delta, + ) + for cr_id in test_cred_rev_ids + ] + + with self.assertRaises(test_module.IndyIssuerError): # missing attribute + cred_json, revoc_id = await self.issuer.create_credential( + test_schema, + test_offer, + test_request, + {}, + ) + + (cred_json, cred_rev_id) = await self.issuer.create_credential( # main line + test_schema, + test_offer, + test_request, + test_values, + REV_REG_ID, + "/tmp/tails/path/dummy", + ) + mock_indy_create_credential.assert_called_once() + ( + call_wallet, + call_offer, + call_request, + call_values, + call_etc1, + call_etc2, + ) = mock_indy_create_credential.call_args[0] + assert call_wallet is self.wallet.handle + assert json.loads(call_offer) == test_offer + assert json.loads(call_request) == test_request + values = json.loads(call_values) + assert "attr1" in values + + mock_indy_revoke_credential.return_value = json.dumps(TEST_RR_DELTA) + mock_indy_merge_rr_deltas.return_value = json.dumps(TEST_RR_DELTA) + (result, failed) = await self.issuer.revoke_credentials( + REV_REG_ID, tails_file_path="dummy", cred_rev_ids=test_cred_rev_ids + ) + assert json.loads(result) == TEST_RR_DELTA + assert not failed + assert mock_indy_revoke_credential.call_count == 2 + mock_indy_merge_rr_deltas.assert_called_once() + + @async_mock.patch("indy.anoncreds.issuer_create_credential") + @async_mock.patch.object(test_module, "create_tails_reader", autospec=True) + @async_mock.patch("indy.anoncreds.issuer_revoke_credential") + @async_mock.patch("indy.anoncreds.issuer_merge_revocation_registry_deltas") + async def test_create_revoke_credentials_x( + self, + mock_indy_merge_rr_deltas, + mock_indy_revoke_credential, + mock_tails_reader, + mock_indy_create_credential, + ): + test_schema = {"attrNames": ["attr1"]} + test_offer = { + "schema_id": SCHEMA_ID, + "cred_def_id": CRED_DEF_ID, + "key_correctness_proof": {"c": "...", "xz_cap": "...", "xr_cap": ["..."]}, + "nonce": "...", + } + test_request = {"test": "request"} + test_values = {"attr1": "value1"} + test_cred = { + "schema_id": SCHEMA_ID, + "cred_def_id": CRED_DEF_ID, + "rev_reg_id": REV_REG_ID, + "values": {"attr1": {"raw": "value1", "encoded": "123456123899216581404"}}, + "signature": {"...": "..."}, + "signature_correctness_proof": {"...": "..."}, + "rev_reg": {"accum": "21 12E8..."}, + "witness": {"omega": "21 1369..."}, + } + test_cred_rev_ids = ["42", "54", "103"] + test_rr_delta = TEST_RR_DELTA + mock_indy_create_credential.side_effect = [ + ( + json.dumps(test_cred), + cr_id, + test_rr_delta, + ) + for cr_id in test_cred_rev_ids + ] + + with self.assertRaises(test_module.IndyIssuerError): # missing attribute + cred_json, revoc_id = await self.issuer.create_credential( + test_schema, + test_offer, + test_request, + {}, + ) + + (cred_json, cred_rev_id) = await self.issuer.create_credential( # main line + test_schema, + test_offer, + test_request, + test_values, + REV_REG_ID, + "/tmp/tails/path/dummy", + ) + mock_indy_create_credential.assert_called_once() + ( + call_wallet, + call_offer, + call_request, + call_values, + call_etc1, + call_etc2, + ) = mock_indy_create_credential.call_args[0] + assert call_wallet is self.wallet.handle + assert json.loads(call_offer) == test_offer + assert json.loads(call_request) == test_request + values = json.loads(call_values) + assert "attr1" in values + + def mock_revoke(_h, _t, _r, cred_rev_id): + if cred_rev_id == "42": + return json.dumps(TEST_RR_DELTA) + if cred_rev_id == "54": + raise IndyError( + error_code=ErrorCode.AnoncredsInvalidUserRevocId, + error_details={"message": "already revoked"}, + ) + raise IndyError( + error_code=ErrorCode.UnknownCryptoTypeError, + error_details={"message": "truly an outlier"}, + ) + + mock_indy_revoke_credential.side_effect = mock_revoke + mock_indy_merge_rr_deltas.return_value = json.dumps(TEST_RR_DELTA) + (result, failed) = await self.issuer.revoke_credentials( + REV_REG_ID, tails_file_path="dummy", cred_rev_ids=test_cred_rev_ids + ) + assert json.loads(result) == TEST_RR_DELTA + assert failed == ["54", "103"] + assert mock_indy_revoke_credential.call_count == 3 + mock_indy_merge_rr_deltas.assert_not_called() + + @async_mock.patch("indy.anoncreds.issuer_create_credential") + @async_mock.patch.object(test_module, "create_tails_reader", autospec=True) + async def test_create_credential_rr_full( + self, + mock_tails_reader, + mock_indy_create_credential, + ): + test_schema = {"attrNames": ["attr1"]} + test_offer = { + "schema_id": SCHEMA_ID, + "cred_def_id": CRED_DEF_ID, + "key_correctness_proof": {"c": "...", "xz_cap": "...", "xr_cap": ["..."]}, + "nonce": "...", + } + test_request = {"test": "request"} + test_values = {"attr1": "value1"} + test_credential = {"test": "credential"} + test_cred_rev_id = "42" + test_rr_delta = TEST_RR_DELTA + mock_indy_create_credential.side_effect = AnoncredsRevocationRegistryFullError( + error_code=ErrorCode.AnoncredsRevocationRegistryFullError + ) + + with self.assertRaises(IndyIssuerRevocationRegistryFullError): + await self.issuer.create_credential( + test_schema, + test_offer, + test_request, + test_values, + ) + + @async_mock.patch("indy.anoncreds.issuer_create_credential") + @async_mock.patch.object(test_module, "create_tails_reader", autospec=True) + async def test_create_credential_x_indy( + self, + mock_tails_reader, + mock_indy_create_credential, + ): + test_schema = {"attrNames": ["attr1"]} + test_offer = { + "schema_id": SCHEMA_ID, + "cred_def_id": CRED_DEF_ID, + "key_correctness_proof": {"c": "...", "xz_cap": "...", "xr_cap": ["..."]}, + "nonce": "...", + } + test_request = {"test": "request"} + test_values = {"attr1": "value1"} + test_credential = {"test": "credential"} + test_cred_rev_id = "42" + test_rr_delta = TEST_RR_DELTA + + mock_indy_create_credential.side_effect = IndyError( + error_code=ErrorCode.WalletInvalidHandle + ) + + with self.assertRaises(test_module.IndyIssuerError): + await self.issuer.create_credential( + test_schema, + test_offer, + test_request, + test_values, + ) + + @async_mock.patch("indy.anoncreds.issuer_create_and_store_revoc_reg") + @async_mock.patch.object(test_module, "create_tails_writer", autospec=True) + async def test_create_and_store_revocation_registry( + self, mock_indy_tails_writer, mock_indy_rr + ): + mock_indy_rr.return_value = ("a", "b", "c") + ( + rr_id, + rrdef_json, + rre_json, + ) = await self.issuer.create_and_store_revocation_registry( + TEST_DID, CRED_DEF_ID, "CL_ACCUM", "rr-tag", 100, "/tmp/tails/path" + ) + assert (rr_id, rrdef_json, rre_json) == ("a", "b", "c") + + @async_mock.patch("indy.anoncreds.issuer_merge_revocation_registry_deltas") + async def test_merge_revocation_registry_deltas(self, mock_indy_merge): + mock_indy_merge.return_value = json.dumps({"net": "delta"}) + assert {"net": "delta"} == json.loads( + await self.issuer.merge_revocation_registry_deltas( + {"fro": "delta"}, {"to": "delta"} + ) + ) diff --git a/aries_cloudagent/indy/sdk/tests/test_profile.py b/aries_cloudagent/indy/sdk/tests/test_profile.py new file mode 100644 index 0000000000..8047d4cea6 --- /dev/null +++ b/aries_cloudagent/indy/sdk/tests/test_profile.py @@ -0,0 +1,86 @@ +import asyncio +import logging + +from asynctest import mock as async_mock +import pytest + +from ....config.injection_context import InjectionContext +from ....core.error import ProfileError +from ....ledger.indy import IndySdkLedgerPool +from ..profile import IndySdkProfile +from ..wallet_setup import IndyOpenWallet, IndyWalletConfig + + +@pytest.fixture +async def open_wallet(): + opened = IndyOpenWallet( + config=IndyWalletConfig({"name": "test-profile"}), + created=True, + handle=1, + master_secret_id="master-secret", + ) + with async_mock.patch.object(opened, "close", async_mock.CoroutineMock()): + yield opened + + +@pytest.fixture() +async def profile(open_wallet): + context = InjectionContext() + context.injector.bind_instance(IndySdkLedgerPool, IndySdkLedgerPool("name")) + profile = IndySdkProfile(open_wallet, context) + + yield profile + + # Trigger finalizer before event loop fixture is closed + profile._finalizer() + + +@pytest.mark.asyncio +async def test_properties(profile: IndySdkProfile): + assert profile.name == "test-profile" + assert profile.backend == "indy" + assert profile.wallet and profile.wallet.handle == 1 + + assert "IndySdkProfile" in str(profile) + assert profile.created + assert profile.wallet.created + assert profile.wallet.master_secret_id == "master-secret" + + with async_mock.patch.object(profile, "opened", False): + with pytest.raises(ProfileError): + await profile.remove() + + with async_mock.patch.object(profile.opened, "close", async_mock.CoroutineMock()): + await profile.remove() + assert profile.opened is None + + +def test_settings_genesis_transactions(open_wallet): + context = InjectionContext( + settings={"ledger.genesis_transactions": async_mock.MagicMock()} + ) + context.injector.bind_instance(IndySdkLedgerPool, IndySdkLedgerPool("name")) + profile = IndySdkProfile(open_wallet, context) + + +def test_settings_ledger_config(open_wallet): + context = InjectionContext(settings={"ledger.ledger_config_list": True}) + context.injector.bind_instance(IndySdkLedgerPool, IndySdkLedgerPool("name")) + profile = IndySdkProfile(open_wallet, context) + + +def test_read_only(open_wallet): + context = InjectionContext(settings={"ledger.read_only": True}) + context.injector.bind_instance(IndySdkLedgerPool, IndySdkLedgerPool("name")) + ro_profile = IndySdkProfile(open_wallet, context) + + +def test_finalizer(open_wallet, caplog): + def _smaller_scope(): + profile = IndySdkProfile(open_wallet) + assert profile + + with caplog.at_level(logging.DEBUG): + _smaller_scope() + + assert "finalizer called" in caplog.text diff --git a/aries_cloudagent/indy/sdk/tests/test_util.py b/aries_cloudagent/indy/sdk/tests/test_util.py new file mode 100644 index 0000000000..be3f5ee36b --- /dev/null +++ b/aries_cloudagent/indy/sdk/tests/test_util.py @@ -0,0 +1,46 @@ +import pytest + +from shutil import rmtree + +import indy.blob_storage + +from asynctest import mock as async_mock, TestCase as AsyncTestCase + +from ...util import indy_client_dir, generate_pr_nonce + +from ..util import create_tails_reader, create_tails_writer + + +@pytest.mark.indy +class TestIndyUtils(AsyncTestCase): + TAILS_HASH = "8UW1Sz5cqoUnK9hqQk7nvtKK65t7Chu3ui866J23sFyJ" + + def tearDown(self): + tails_dir = indy_client_dir("tails", create=False) + rmtree(tails_dir, ignore_errors=True) + + async def test_tails_reader(self): + tails_dir = indy_client_dir("tails", create=True) + tails_local = f"{tails_dir}/{TestIndyUtils.TAILS_HASH}" + + with open(tails_local, "a") as f: + print("1234123412431234", file=f) + + with async_mock.patch.object( + indy.blob_storage, "open_reader", async_mock.CoroutineMock() + ) as mock_blob_open_reader: + result = await create_tails_reader(tails_local) + assert result == mock_blob_open_reader.return_value + + rmtree(tails_dir, ignore_errors=True) + with self.assertRaises(FileNotFoundError): + await create_tails_reader(tails_local) + + async def test_tails_writer(self): + tails_dir = indy_client_dir("tails", create=True) + assert await create_tails_writer(tails_dir) + + rmtree(tails_dir, ignore_errors=True) + + async def test_nonce(self): + assert await generate_pr_nonce() diff --git a/aries_cloudagent/indy/sdk/tests/test_verifier.py b/aries_cloudagent/indy/sdk/tests/test_verifier.py new file mode 100644 index 0000000000..d4abc1bdd1 --- /dev/null +++ b/aries_cloudagent/indy/sdk/tests/test_verifier.py @@ -0,0 +1,596 @@ +import json +import pytest + +from copy import deepcopy + +from asynctest import mock as async_mock, TestCase as AsyncTestCase +from indy.error import IndyError + +from ....core.in_memory import InMemoryProfile +from ....ledger.multiple_ledger.ledger_requests_executor import ( + IndyLedgerRequestsExecutor, +) + +from ..verifier import IndySdkVerifier + + +INDY_PROOF_REQ_NAME = { + "nonce": "15606741555044336341559", + "name": "proof_req", + "version": "0.0", + "requested_attributes": { + "19_uuid": { + "name": "Preferred Name", + "restrictions": [{"cred_def_id": "LjgpST2rjsoxYegQDRm7EL:3:CL:19:tag"}], + } + }, + "requested_predicates": {}, + "non_revoked": {"from": 1579892963, "to": 1579892963}, +} +INDY_PROOF_NAME = { + "proof": { + "proofs": [ + { + "primary_proof": { + "eq_proof": { + "revealed_attrs": { + "preferredname": "94607763023542937648705576709896212619553924110058781320304650334433495169960" + }, + "a_prime": "...", + "e": "...", + "v": "...", + "m": {"master_secret": "...", "musthave": "..."}, + "m2": "...", + }, + "ge_proofs": [], + }, + "non_revoc_proof": None, + } + ], + "aggregated_proof": {"c_hash": "...", "c_list": [[1, 152, 172, 159]]}, + }, + "requested_proof": { + "revealed_attrs": { + "19_uuid": { + "sub_proof_index": 0, + "raw": "Chicken Hawk", + "encoded": "94607763023542937648705576709896212619553924110058781320304650334433495169960", + } + }, + "self_attested_attrs": {}, + "unrevealed_attrs": {}, + "predicates": {}, + }, + "identifiers": [ + { + "schema_id": "LjgpST2rjsoxYegQDRm7EL:2:non-revo:1579888926.0", + "cred_def_id": "LjgpST2rjsoxYegQDRm7EL:3:CL:19:tag", + "rev_reg_id": "LjgpST2rjsoxYegQDRm7EL:4:LjgpST2rjsoxYegQDRm7EL:3:CL:18:tag:CL_ACCUM:0", + "timestamp": 1579892963, + } + ], +} + +INDY_PROOF_REQ_PRED_NAMES = { + "nonce": "12301197819298309547817", + "name": "proof_req", + "version": "0.0", + "requested_attributes": { + "18_uuid": { + "names": [ + "effectiveDate", + "jurisdictionId", + "endDate", + "legalName", + "orgTypeId", + ], + "restrictions": [{"cred_def_id": "LjgpST2rjsoxYegQDRm7EL:3:CL:18:tag"}], + "non_revoked": {"from": 1579892963, "to": 1579892963}, + } + }, + "requested_predicates": { + "18_id_GE_uuid": { + "name": "id", + "p_type": ">=", + "p_value": 4, + "restrictions": [{"cred_def_id": "LjgpST2rjsoxYegQDRm7EL:3:CL:18:tag"}], + "non_revoked": {"from": 1579892963, "to": 1579892963}, + }, + "18_busid_GE_uuid": { + "name": "busId", + "p_type": ">=", + "p_value": 11198760, + "restrictions": [{"cred_def_id": "LjgpST2rjsoxYegQDRm7EL:3:CL:18:tag"}], + "non_revoked": {"from": 1579892963, "to": 1579892963}, + }, + }, +} + +INDY_PROOF_PRED_NAMES = { + "proof": { + "proofs": [ + { + "primary_proof": { + "eq_proof": { + "revealed_attrs": { + "effectivedate": "29898645652691622994103043707423726726370719600737126045061047957925549204159", + "enddate": "102987336249554097029535212322581322789799900648198034993379397001115665086549", + "jurisdictionid": "1", + "legalname": "106000828387368179355563788886235175190145445419967766011746391966411797095112", + "orgtypeid": "2", + }, + "a_prime": "15004053730967415956444780426929245426212215338862984979494015601906315582840747306504594441147441231491693951307278868750626954115009843921712832446544313561614118651520859494434080523236571958503756731624044004577892061145780190353067193496632483652558392939182437813999901132281095751156851493821923092362225145694407463842363472935374563198989094026343235461171230866928987229440390088485243428084237480758852248249770191814659757536925909483623366615812343227994433513635227834136882757394235805747686707186194818800509367263735891033464810268941516104197138946893490285348940539273855011764023467736767154303840", + "e": "337235637061060569047727820825037317712308782222370290484075504679799877011498224496826887984625822621748120654975531604507028064312710", + "v": "1404574530639210172781710601270953478414552186112985513475784201805119858770941821816370201652610496512142438596496007803868074196519046400754620766301997215969127187833134416898687892635798644651536667962849945968608408680347359937747715599800353850666709655353571249823190377976481837350280859973291095846106817174217510384400072134061086282647508604512946800721425580606901739211324799734725830882957974114794011791236006123406974194631084620463349145771704097181067806553409326685136263002734388842031423620455266365851581883497063570426034222596154597920580362387253753317413050267993785894175555001456331223234400596625730555935567545822248399326429854362227165802672227905967242505077485029657064067770641969647406371744932313880132835781524174868335472062214928455106355639700336515509695339440337721239602256387991397127509846614577908429409389332146746885470613002111095896313068354016587589778644661193149185049", + "m": { + "master_secret": "268741899404098839327031223989228937242803085201179726908056281850709820406283972250249379228789368664433583241086441517910928033266925485611584652328638784395957058632060633630", + "busid": "2687197004064133543257369626470144380098036289489284489320086515026620206692616047425976133587124290887441908383692364439260071404270430528078491104384060203570606253676528361400", + "id": "6686713960986576137959547581149682718587656100042127047852747024276212127252400140409890726173570723819441146289878657604374865560165489933600436341950054222778208816245032311193", + }, + "m2": "1180732317592917288409508571561928260151012766032216949553655321777067495114084046539503538100319204468787213948625648828272873800122130063408401311370987", + }, + "ge_proofs": [ + { + "u": { + "0": "15775609194986735920510151800942995799222803216082415810148803816296803079801357496664353906579826995829149362968465835795491271435248746459334118965204125314594582971550392227954", + "2": "5303150256152520023495176881750201750170184894234097909710215547442554470805609846521764595898911334528530696240025838754931022084938196723161868181531727845300439592437899863887", + "3": "3356711078459696620189646681109895593397921422760359051406583045001333345458592898545852513866307624143916692556089833035405496562577023756005223378326300905996972689863856066875", + "1": "9999991890173781186974768504758157527548652482914116775165195164578745484991479122468109103928320060297494255214338396428491092606606051561499468708339979065194763516537003502062", + }, + "r": { + "DELTA": "1435090146724677611480872211988213747514582597135551797832629955760022689079479873681839403744643599039883834204615937515288097736927712499250203649611222666450687692819628191366070914555251320872315378202337414304735555708434851449005494065128333408295370378734399236857073675785782330461793283646613324794741612075132251003819809779185772616720952264615331933630593162857145006881047266047864525898689246790061753791575361871922643386721142202508891103428155725164118848489256450446385140752308548079412012057535799088600334139468432242004848224338715577616491890083382988533414746224157485737168172255373805848589505587117269746736884630803388115258573174965628402748672653106950672620945656786479587908733067646954", + "2": "1256008392268065460119207279471943768595068414860014149178278435399371400930962253759162888062020269227529923329167742931240473191566641468995735758696802138379814852469042293401437137956018945170092817785505583108837356735852654194156319248319729732466320634812831259869290012647072233952795236462156213752008019954481267917886292498492055846838619027167304191382208021540244250507570410888356448310603088364351895116324030484480320862223729665151544010778941061938440283026451178172286282191179070116776836046514823559685428369094109958537683453915206656040875788707049636999992215238431149080323327641760705766913474027428111260788981734182250598573031877786769378931547452684486997457718460021235476398326984192784", + "0": "1106819115015372998825208031668263762670044285179584053573615157030968633235403572251376628759852167093842839880087972608252236859761641404161906797947710722723839146345785722305071566665021165225324459369096631275993154775947819333058721055644040212303789659749985973470385218248603826878093609862767077416104661216163222886987607841769251824856950498177308519655255200045046063857789306026581362754683975573850037114774186895901788964351934533171525025276070855270632786783019588176816118329221122549398793872640055312900842112041891936494042853798319986365192512964078607266631918748545903216736690057842950922926661694759259530843862322858400156976838479950950178486526234308178957984785053903260967594398611911474", + "3": "1344309321242892215222847471501532826517184846819833677474602495849657271930678291855112591971466462816524573183554788643533109793416521709602069842696124889558288092186793062177006244758779556603409762571362221142413760629539533275654542467194539359678435299002566931998816165917234259226849828723125451685169672272552524344813036153633311318760938874320338280443847065712732394378892985736654998112090834297537844732478643713076998558297751199030671616253345870616092528684635775411928128373368327191277066131632614473951005152162823879892345970535505519113833062530738837915987508410926372810518540478552946670006272356196419957933718303344632112441115930033837912179851905872564389256853587645059720488720795906498", + "1": "601693817301763663113031272722721908754633550776510238958619960119672962844730314111437951375084589705366750107667669458320527173785853103929038523863706300574327283273485302578112396814149563941340189390051835244496748959403476105143355455812570759887645896592114448469303958006046663589996470308366068555479184906610439541373120510006128200782324694975090482529033281708168823833732457689747330091963586305323138559502300486975246644545238909598413498252470653544977963083975726047754021026165970401681664501179243533611966433308438886961268871140737772352678991735861225177227793364352974323624694500485545573621034350559474030565509027433457718466600471361048730018443642651540442802817989514889987171548775560085", + }, + "mj": "2687197004064133543257369626470144380098036289489284489320086515026620206692616047425976133587124290887441908383692364439260071404270430528078491104384060203570606253676528361400", + "alpha": "55264634475788812054149982413198771839810724235465324658821557285735947681415835295178267002738090787902834904063083682990582592095393028970773939852521059447360650213986737569312363077820486616943853008592650537183003498185887824618357246364458614494253289122927160626742649252943244636915456680482390825080294565093068093917997001255757200832353046300646785756652682640188703523223073037006585218839054980180609464837830370157522462983934135435603408143309318659202555550473599548567996557919032937165600303958449173855781262863161799425917680286809410314205550551542955745937751254083650878398344461109371177805333303453760504594222290495116260958547048583654306199387054245295488649024179114894686831993370968945510894767150406222332165620064150891563554498413420757277508788138394747656372783710437243804659113648361274361422790365575", + "t": { + "2": "1276353167840913477021397624773394332173592088650367702185572394040398533199538101776458275797662881371280361310311170677242402214354355702620614537036611922064060504606618126681639882263139365680565350790281701009940301284340534766480451762902788628875609130151618956111512660983755135355570760793108220842022869639781026918247205511538713530652099730605791686827103126406846076633375908411453922078354225032716111673736810973402770388177401531928271370790938081733309345905963052715943136682338494175330354955277424030755355371412956250746882945100461786601740318616758180741835591171045104436982446340050589105952", + "0": "52506109491039096251755479392960889070840776962363540274456217953760113102006029814040519995494713986268145627084927516727099691151450378385140332116480118436738261593744184296007314732823898043080011956933010369575980799348117283597824162615912372823633177749168952698401203464607973674241038357379577293158404669765882906589960120865518413803711729942613061301420107178603192154873722316947550106277771120767826035047479123749931790881679576800340417944013614994751361795012191068369383577242249201927422484806926120532089036692818076818060938822432774203557319821915034796962936855918437128832683302834778450852076", + "1": "113031374658594175812052384858113115052077482873081996361152721528334589441352531310470368095073157716273853401381658707580502108484382463859531044307244944300120928991532655473230562771713806228238940140492981669914382036157400059197253018428984542349187927786210979478008036674432605219414300881116700073904513558719492127462395417843765324361843076852973933175787635618464392198807598044268223652564648024618437362752148593227485835178720349721798423100634521510710239416375840314170338898512726956877281625226003452828033987655579773273571285524048285234475184043290899568731903112287738739915600509899766360789888", + "DELTA": "48234611140928682288937615809872962358698394776719271528059766394227502012090856649758227578113306604028516575292703546202775777621049060595611852517094547384541819122623967215175704296901562660240718967260151010609870475975072516070346770954330313963878747194405855012585768501635077016535846206257741952202337055842434195875166686634891739392750890861333875772954056854354284061793365725202163447856793288398072711194949704852318180275797984445814279136152858759907525062790250675184786142234427994860090376938644764493873560454829155051260697226196758890394411645758956396137763703934929680277278644873416013261035", + "3": "89994605437763910628730772379416923861874648327020237340785010128698483324987645925227420742287632948945347297152300219419713493590120999381541274609870183955909628256613851122899039933589797230083354701292272442523280565440597314568786864750837443422276701528731625877274094155541825495114060437788769205202442723879088098866185978654728309516302335284177924161235100925296934812127598754913984676011716654143603885735997160890946409226842054727795290304131313120189570773196857529159798597569189742987994905034522112705638567954037460125246215182613760404547369876267284411245030884496328403051974209422359756153509", + }, + "predicate": { + "attr_name": "busid", + "p_type": "GE", + "value": 11198760, + }, + }, + { + "u": { + "0": "13639548796026429922431564475630909149287414026758460721805236736313279517016438050089911517098811596997747189614439260518531845477684148307804856579405503329745365642794423965550", + "2": "12692415150154152887167590190910159618471206042982658652940787170770193806407265717354418163057121876574358366510055892372348735991661901637525227498965237677355250159501068181772", + "3": "6699159556719214469836363462599679663866420825429540116943002714507804742697411533141864346616123740789790632843719915716457061440487115732563925309886301301835201778554620543295", + "1": "2018654799729593932888298230804022878883145101317651811950082851492082577094184498971399238402895197739207931768086301073280634251050932415705600476284738694155135236800581664160", + }, + "r": { + "1": "825587756964975640126314737718300012891046538726331178577448524710910340957817679849290109848304786342311186386453239759474660538454793939540876256076287017677140704068118361949660090673111340635478762304690817532764517905140299716866605223450803768338360729151901747687349983483402342999368967231581939563361347289212973086454185400770130710116840233323953976914342262402301362679497329671787598650893202541829399630505463177921655009726556920408538662140155815031458475909120161960047235187953148398737965729023268444789967620657212914775071615366971436269789139928904779054710447116218434690464549160131819794059427689273427325814904354192089075836597740878803445045080385629565176143354201573860707045668850877586", + "2": "1466408189748340763973829793343949568330918709265623621614464341218317503955515434953266875378586538446326464353600075579788794127665478299651259465473747112701101990004860122720151191106445704432013015062973865716673386400413561687311954374930156679604666267815298214479078026652043482916898087471155683856282470644588563159648375551108786970597383143516158031628710096807215305878905007543811401502472821013567629888746492557864905681554913361277548019219082051265255078152509205293776781790132507115787621452248689332496610099725566623311760857590035073594921664074567131690599897210005475078142722295326868452002437292574903183037228401231409631285848202575278151773369676950274790626198680132560950102001994557758", + "0": "993248502537248262082444202395290853332499246354083708269674970707520839045168624341335318664418224639164402187209309139427257892643191846187663592057257899679944076599283980872521437340751206357777926871742796186382563827967273141200749480590415594087209691507734426984052841712131263160951495974745152392404724577427973267669378931113495076274617344076060846279028767371296979484895771867209047720463195305161885422275388748188299814182891315332800557749699941587327916028930365349641271736635219800975554147836564077611147631789530042925759823398087582121686407890628257624663383236878047170688254415445440912626941967028065807021170264150964938678824504194752040131898249057197187446968567390619785928296680096859", + "3": "353677912339120670248802964352055631737613331947764251954000578577314223482877266750851861467829550374246392637478716468616296688578414836737374015352059254057436572686513161681724599053168679581126352074962010335889993562619355121275432902043064229165956511160994192882167562213269670332262473472293819501037932879123080023576285854568501212240875918139761976977842939660466373041805369493971290555885442554468124891943099059169515428968196495673746803133324864149723509564523971808556630671471618581233229134929554792186889060256901637092067130348403992303346483664985586122149628146304160243882639275298266216270358565584574585823864941692911554602002331492551293859949912337984877479524597804956696499812250631744", + "DELTA": "725960022886687948013207416539699149371621853290822104811918058808196468403337509381122781137942343897440199450987104988666229964851227549448628470704889721866971126265999067769808855341632931627785927114398786533559660381398895352266657934136549351825103362166280268159652759301507640976500533521688660251972577237532256300306442315564311264115224457865178259661593100327194825492692234619818096596609477148829377559407992257373097100180145505767561403356284282388735420784241021016181364636135275395790815788682767997871662899508826815736302921531147145381730507095314577476550947092200539059112480501048978059997520366967856033897452966490827003353334313372398949710717623991939354590550708881302450618430658953556", + }, + "mj": "6686713960986576137959547581149682718587656100042127047852747024276212127252400140409890726173570723819441146289878657604374865560165489933600436341950054222778208816245032311193", + "alpha": "54312418620368017823413444392364697511954099195960921763340447211826963863335156427497199363801396644023918819073111783414569539781613083737557451917064836287105910850065875966424715696905777194811070071499968289670886194094119693198719726955718469770568556811780821894560508716495412081426259363632509089906620904790770113618876886851367553577555822830824722182497761114967889600421573310792308390968429341290356015872285765321156360499004114406293720515635636721256956836801168192621092752489119545742530767529595705696014856308531466145146269599634259697543058622520958051728230537251854292098956994695268415292349999637984082162556184322623578612708830627477718675001902228134597558345283147625462346943667586065769392740787755841399970302076200764539143397370091692013055692886714129148712005056929884477612627289722508451690081998890", + "t": { + "3": "76842650074027971332631982512373611181628371639695357946107030911055453488768447213460618917725534086368162318588252003797289252473279448248400376609193928062810022884201102892017821282461806593568305060473753735848560048445524907113838106958747793434918052694775405184619214354190540002998204225798499364075579094271521191419027986291013493577021803670203051346914082929873231509819450163988354047777312127725561922611471445963909565688013793926876707562644935391518355932605047591545917637465241017629839541260483606708345518662351776889719949822005165906622964213143757683950646046295114922019124075069329268061942", + "2": "104991045405164906316724339229643785709360971949973916361929774804163421784479300621496063132861029493850348596359070365652827572699577454378465299784873962729586537933990712981855548459986825452865420618489151243413027040820258308949176618728507177438646401022030966936494703173837070422031040550750643315987178063356959004909489540688791639398005266908038895531691252968451136025538449648159989963830846794193607106472742567850015960071634812903985081979755017126350806404047244177458032873066418448813920685609285163826032405474833353441325867090653794998832828049943461795570528006274431422907140560130037296666626", + "0": "93372553202246858510371387492221683266873274595585378473760313800346458391438909787465170333251843314544241604938410847073082151810448655558028921073676767770394665021417900636520680814177493616534162641758512946743051333557436759523671912141418810442158225543010238061117969558203880853763255647243160765086932831295304550412607848190595598510980669944139696363322475177492264636536910201776020324858798972778323663795303339939472573927415127166116444898790357846635883222746031584554927383535016321617425087697872601850303134185636960112124926520878185699975818343081756286170638877967660840814776000077787223928056", + "1": "88156501142569212422367853754801651086852287000049991938144173063936879655987557042149874374683234911554850776079721311154420204826376746982087019508277766132575858575556680538019849786965963718649479179859820106973881788608463705644074554956818391872137271784803047333543321479251515998725336896820211102747123583803854741248907240437683401575881169746704849524328003061107258995982062254548684849294595639491104266371155934951313704136302996039897528196270331875472554549327417349243990461246127383357748906616773662459665620147625796186736530089927957522542298814250937114283836911153790542409683746775259226224961", + "DELTA": "6178997688515360528852083990605883033892934661031543684879979804577432521872124008044788245406591933749401429548633356472853716766388636618335206416158216292785839570827245139150787585027801572977051847786797012358936548986405917266204321163760568135873831346087680040040301251630530064206552793273933549993844498438010903850013120770715837075880286264742500598494248510064600863411203212869270221192303957773402376357672080257393369434247825409313396018869267942811592657266119556377402842108726474978400793026037873416208879964428023321485607453655856252140587803891157033568210852205447175844430607889546700526279", + }, + "predicate": { + "attr_name": "id", + "p_type": "GE", + "value": 4, + }, + }, + ], + }, + "non_revoc_proof": { + "x_list": { + "rho": "12B28F49BF5F2CDA105B904CD594EB5B5F558025CDDB7D0F3057D19830F81694", + "r": "010F2B872DC4BECAE085D9FA1FB98184C3E00181A678F2B256140482B4DEDFCE", + "r_prime": "1AAF1AB0071B64FE22AC42219962B9ABA02147B09DFDC4C1FD088E6706D312FC", + "r_prime_prime": "1630D0800ADE33816BCA96EE89EC1E312BE80220510FAFAAC52BED567B187017", + "r_prime_prime_prime": "14D06B2F7B369880821DAAFD40D74FE3B495EE3A7CB7E937FDC4E9707330F301", + "o": "147F16718A0CCB2EC137ECA3952C801FB2281B06CB4ADC24092CE31CA2EAC5AD", + "o_prime": "14F4668810341353E0C25485C4F3CF459BCB69DD74FF73376A23ACAA550E09C5", + "m": "0EAC754EE9AC81C495AC3BB302511034965965AF337BC4F43850A1636E70314E", + "m_prime": "07CA764055E9920E7C4417C3A63BF610C145F25D618A41DAC785210D7320F0EF", + "t": "199D84A1133FB103E4E77CC6F917A12355AD4A29DCCC24999A2C67EBD62B5306", + "t_prime": "07154F39841E3D75E1E07179875B94D655E3BDD4A349E0BBAA43422CC527AACB", + "m2": "0D4380FF8ACDC21611BC8AB83127950441DA42A49A347BEC6F32044F033D3017", + "s": "0A65AE9D0C0D4CDAA5D4EECB48BC6DFD2279BD2C040AC0D9011918A9E0A7A866", + "c": "0ABFC02DDF76995C48CADEE8447665EB933446FEC42E7074FB11720E141CFC07", + }, + "c_list": { + "e": "6 418D8713ED93CD8C065EA42D110C581C2CE07A58771077B1C2016E53AA2E7461 4 2032A4917D0877B9723CDCD82B32AC96C534B0CAA5ED2EE3FFD605214511CB1F 4 0D8E5DA074651A0DE91F86F663F071EA4D4CD4CBA438F7A4D182C8D23D01B485", + "d": "6 37635F35298D224C0E3F01EB06DC8AC1D8A7E048027162077E0204801F22FF94 4 1E64440E13B08BD249B5C35B637C70BDA471926F5F3896400ED25EDA4678B73D 4 3A5BB704B473894CD54C91D1D159A7BD8FA8092545F93D1BC195D52D3EC96EDE", + "a": "6 6000DC780B9D7C71575A328DE2BACB78A2737E9C1CE64BC8BCE98BD8486EAAB4 4 39555F38DB15EC820DA3A7A61820F831A003D414D4A0EF60D1D37ABD8B5E1070 4 25FBA1AD320F02D9082118E978B4FE261280951BCE1FED15F65771AE04F8E270", + "g": "6 5D293948EF43989ACBB8262B8C7F10A551AD71190D70B9AAA62943C4FE6A4C42 4 2B3E1ED0A00163DCA9AD9B69DDA124290CF8F580F60595B5E9D506A3C0D9A903 4 29C2B6F7AD7F7B223FC40BD9C1147FCE831B26ACB477855C9D3EABD7B0341342", + "w": "21 1371C921AE2377A1CD9F0D3E863B09487B6DFC0DC5F2BA32133E4F5EF2ACA5641 21 10B84BA9167755980B1DCD97AB698D56E3C9CDCBE7A85F0776A2C96B3BE9519BE 6 6676ADACEC607381F87211DAE4DE6A630B74FAF580DBC383D8450C7852BC09C4 4 379C9A4FF46DEBF21223823D5B2323F7A56A394157E94DB95914A9E5BB27FAEC 6 7121D621C85D9BA22371A0862909FF25198F0EF690207AEE3910FB0E0A7A4F62 4 1C052A0276360F0D8AEBA71BD65ECB233FFDB700F031EA03146CF00BC2F2D5B6", + "s": "21 1272F477F5A0F83CCB316DA088F6A6A12C131D0DC9BC699023F534624B8EE255A 21 13816855011465BE2E8972F52EE4692873A763513A764BD92B8B7CBBBAA27D7E8 6 7B190F599B5F0EA53802135BBD655B080743FE60CC22329F69770D6B765F0AAA 4 2AAA191CA59348C6A920BD1D1AE37A7C96F424B6D8E921B54EA5C1C7C56297AA 6 80254CA5DFBAD3C39BC757534922FBD0846AB86500D5D168109EB6B8A9D2BE33 4 1CC93B3769A7BE2AF52CCE391D2BB57F9D907F530038EF84B3EC4AB54D62D872", + "u": "21 11E538813B74EFC8676EF5AC87AA05A0FF58913B7C68E264FCF5ED0D57F6BC781 21 12EE7BE65E15CF4C500E2D92DB02670FBD8B51C6BD0B35AE139E9CE9658B15CC2 6 856B3C0C152F75A449AD73DFAD7DFD87A99AAA606E3D8E392D765E3C987D7B47 4 34245F01BD7C4144DBEBE7AB35303BF02FB5717EC6B080BC9C2C930D929D4ED7 6 8113F127D8762B174DCB32AEE370297BF7CFCCF797510B53D53564AEC9105909 4 3B2434AD9AB4E7ABA7125800D14470A098AE04FA523CB60A6FFF62D371B95E13", + }, + }, + } + ], + "aggregated_proof": { + "c_hash": "37672016063516849654668323232510746418703126727195560560658262517075578769045", + "c_list": [ + [4, 0, 0, 0, 0, 0], + [4, 17, 153, 0, 0, 0, 0], + [4, 0, 0, 0, 0, 0, 0, 0], + [4, 1, 134, 126, 0, 0, 0, 0], + [10, 250, 248, 125, 158, 54, 165, 91, 59, 1], + [4, 167, 169, 22, 44], + [31, 254, 53], + [118, 218, 1, 27, 51, 96], + [1, 254, 120, 236], + [3, 127, 97, 134, 148, 32, 128], + [10, 124, 191, 32], + [32, 59, 96, 254, 165], + [195, 171, 64, 72, 40, 235], + [2, 175, 185, 172, 248], + [2, 152, 166, 185, 65], + [3, 63, 176, 24, 2], + [2, 96, 182, 196, 220, 182, 246], + [48, 242, 116, 58, 18, 199], + ], + }, + }, + "requested_proof": { + "revealed_attrs": {}, + "revealed_attr_groups": { + "18_uuid": { + "sub_proof_index": 0, + "values": { + "effectiveDate": { + "raw": "2018-01-01", + "encoded": "29898645652691622994103043707423726726370719600737126045061047957925549204159", + }, + "endDate": { + "raw": "", + "encoded": "102987336249554097029535212322581322789799900648198034993379397001115665086549", + }, + "jurisdictionId": {"raw": "1", "encoded": "1"}, + "legalName": { + "raw": "Flan Nebula", + "encoded": "106000828387368179355563788886235175190145445419967766011746391966411797095112", + }, + "orgTypeId": {"raw": "2", "encoded": "2"}, + }, + } + }, + "self_attested_attrs": {}, + "unrevealed_attrs": {}, + "predicates": { + "18_busid_GE_uuid": {"sub_proof_index": 0}, + "18_id_GE_uuid": {"sub_proof_index": 0}, + }, + }, + "identifiers": [ + { + "schema_id": "LjgpST2rjsoxYegQDRm7EL:2:bc-reg:1.0", + "cred_def_id": "LjgpST2rjsoxYegQDRm7EL:3:CL:18:tag", + "rev_reg_id": "LjgpST2rjsoxYegQDRm7EL:4:LjgpST2rjsoxYegQDRm7EL:3:CL:18:tag:CL_ACCUM:0", + "timestamp": 1579892963, + } + ], +} + +REV_REG_DEFS = { + "LjgpST2rjsoxYegQDRm7EL:4:LjgpST2rjsoxYegQDRm7EL:3:CL:18:tag:CL_ACCUM:0": { + "txnTime": 1500000000 + } +} + + +@pytest.mark.indy +class TestIndySdkVerifier(AsyncTestCase): + def setUp(self): + self.ledger = async_mock.MagicMock( + get_credential_definition=async_mock.CoroutineMock( + return_value={ + "...": "...", + "value": { + "revocation": { + "g": "1 ...", + "g_dash": "1 ...", + "h": "1 ...", + "h0": "1 ...", + "h1": "1 ...", + "h2": "1 ...", + "htilde": "1 ...", + "h_cap": "1 ...", + "u": "1 ...", + "pk": "1 ...", + "y": "1 ...", + } + }, + } + ) + ) + mock_profile = InMemoryProfile.test_profile() + context = mock_profile.context + context.injector.bind_instance( + IndyLedgerRequestsExecutor, IndyLedgerRequestsExecutor(mock_profile) + ) + self.verifier = IndySdkVerifier(mock_profile) + assert repr(self.verifier) == "" + + @async_mock.patch("indy.anoncreds.verifier_verify_proof") + async def test_verify_presentation(self, mock_verify): + mock_verify.return_value = "val" + + with async_mock.patch.object( + self.verifier, "pre_verify", async_mock.CoroutineMock() + ) as mock_pre_verify, async_mock.patch.object( + self.verifier, "non_revoc_intervals", async_mock.MagicMock() + ) as mock_non_revox, async_mock.patch.object( + IndyLedgerRequestsExecutor, "get_ledger_for_identifier" + ) as mock_get_ledger: + mock_get_ledger.return_value = (None, self.ledger) + INDY_PROOF_REQ_X = deepcopy(INDY_PROOF_REQ_PRED_NAMES) + (verified, msgs) = await self.verifier.verify_presentation( + INDY_PROOF_REQ_X, + INDY_PROOF_PRED_NAMES, + "schemas", + {"LjgpST2rjsoxYegQDRm7EL:3:CL:18:tag": {"value": {"revocation": {}}}}, + REV_REG_DEFS, + "rev_reg_entries", + ) + + mock_verify.assert_called_once_with( + json.dumps(INDY_PROOF_REQ_X), + json.dumps(INDY_PROOF_PRED_NAMES), + json.dumps("schemas"), + json.dumps( + {"LjgpST2rjsoxYegQDRm7EL:3:CL:18:tag": {"value": {"revocation": {}}}} + ), + json.dumps(REV_REG_DEFS), + json.dumps("rev_reg_entries"), + ) + + assert verified == "val" + + @async_mock.patch("indy.anoncreds.verifier_verify_proof") + async def test_verify_presentation_x_indy(self, mock_verify): + mock_verify.side_effect = IndyError(error_code=1) + + with async_mock.patch.object( + self.verifier, "pre_verify", async_mock.CoroutineMock() + ) as mock_pre_verify, async_mock.patch.object( + self.verifier, "non_revoc_intervals", async_mock.MagicMock() + ) as mock_non_revox, async_mock.patch.object( + IndyLedgerRequestsExecutor, "get_ledger_for_identifier" + ) as mock_get_ledger: + mock_get_ledger.return_value = ("test", self.ledger) + (verified, msgs) = await self.verifier.verify_presentation( + INDY_PROOF_REQ_NAME, + INDY_PROOF_NAME, + "schemas", + {"LjgpST2rjsoxYegQDRm7EL:3:CL:19:tag": {"value": {}}}, + REV_REG_DEFS, + "rev_reg_entries", + ) + + mock_verify.assert_called_once_with( + json.dumps(INDY_PROOF_REQ_NAME), + json.dumps(INDY_PROOF_NAME), + json.dumps("schemas"), + json.dumps({"LjgpST2rjsoxYegQDRm7EL:3:CL:19:tag": {"value": {}}}), + json.dumps(REV_REG_DEFS), + json.dumps("rev_reg_entries"), + ) + + assert not verified + + @async_mock.patch("indy.anoncreds.verifier_verify_proof") + async def test_check_encoding_attr(self, mock_verify): + with async_mock.patch.object( + IndyLedgerRequestsExecutor, "get_ledger_for_identifier" + ) as mock_get_ledger: + mock_get_ledger.return_value = (None, self.ledger) + mock_verify.return_value = True + (verified, msgs) = await self.verifier.verify_presentation( + INDY_PROOF_REQ_NAME, + INDY_PROOF_NAME, + "schemas", + {"LjgpST2rjsoxYegQDRm7EL:3:CL:19:tag": {"value": {}}}, + REV_REG_DEFS, + "rev_reg_entries", + ) + + mock_verify.assert_called_once_with( + json.dumps(INDY_PROOF_REQ_NAME), + json.dumps(INDY_PROOF_NAME), + json.dumps("schemas"), + json.dumps({"LjgpST2rjsoxYegQDRm7EL:3:CL:19:tag": {"value": {}}}), + json.dumps(REV_REG_DEFS), + json.dumps("rev_reg_entries"), + ) + assert verified is True + assert len(msgs) == 1 + assert "TS_OUT_NRI::19_uuid" in msgs + + @async_mock.patch("indy.anoncreds.verifier_verify_proof") + async def test_check_encoding_attr_tamper_raw(self, mock_verify): + INDY_PROOF_X = deepcopy(INDY_PROOF_NAME) + INDY_PROOF_X["requested_proof"]["revealed_attrs"]["19_uuid"][ + "raw" + ] = "Mock chicken" + with async_mock.patch.object( + IndyLedgerRequestsExecutor, "get_ledger_for_identifier" + ) as mock_get_ledger: + mock_get_ledger.return_value = ("test", self.ledger) + (verified, msgs) = await self.verifier.verify_presentation( + INDY_PROOF_REQ_NAME, + INDY_PROOF_X, + "schemas", + {"LjgpST2rjsoxYegQDRm7EL:3:CL:19:tag": {"value": {}}}, + REV_REG_DEFS, + "rev_reg_entries", + ) + + mock_verify.assert_not_called() + + assert verified is False + assert len(msgs) == 2 + assert "TS_OUT_NRI::19_uuid" in msgs + assert ( + "VALUE_ERROR::Encoded representation mismatch for 'Preferred Name'" in msgs + ) + + @async_mock.patch("indy.anoncreds.verifier_verify_proof") + async def test_check_encoding_attr_tamper_encoded(self, mock_verify): + INDY_PROOF_X = deepcopy(INDY_PROOF_NAME) + INDY_PROOF_X["requested_proof"]["revealed_attrs"]["19_uuid"][ + "encoded" + ] = "1234567890" + with async_mock.patch.object( + IndyLedgerRequestsExecutor, "get_ledger_for_identifier" + ) as mock_get_ledger: + mock_get_ledger.return_value = (None, self.ledger) + (verified, msgs) = await self.verifier.verify_presentation( + INDY_PROOF_REQ_NAME, + INDY_PROOF_X, + "schemas", + {"LjgpST2rjsoxYegQDRm7EL:3:CL:19:tag": {"value": {}}}, + REV_REG_DEFS, + "rev_reg_entries", + ) + + mock_verify.assert_not_called() + + assert verified is False + assert len(msgs) == 2 + assert "TS_OUT_NRI::19_uuid" in msgs + assert ( + "VALUE_ERROR::Encoded representation mismatch for 'Preferred Name'" in msgs + ) + + @async_mock.patch("indy.anoncreds.verifier_verify_proof") + async def test_check_pred_names(self, mock_verify): + with async_mock.patch.object( + IndyLedgerRequestsExecutor, "get_ledger_for_identifier" + ) as mock_get_ledger: + mock_get_ledger.return_value = ("test", self.ledger) + mock_verify.return_value = True + INDY_PROOF_REQ_X = deepcopy(INDY_PROOF_REQ_PRED_NAMES) + (verified, msgs) = await self.verifier.verify_presentation( + INDY_PROOF_REQ_X, + INDY_PROOF_PRED_NAMES, + "schemas", + {"LjgpST2rjsoxYegQDRm7EL:3:CL:18:tag": {"value": {"revocation": {}}}}, + REV_REG_DEFS, + "rev_reg_entries", + ) + + mock_verify.assert_called_once_with( + json.dumps(INDY_PROOF_REQ_X), + json.dumps(INDY_PROOF_PRED_NAMES), + json.dumps("schemas"), + json.dumps( + {"LjgpST2rjsoxYegQDRm7EL:3:CL:18:tag": {"value": {"revocation": {}}}} + ), + json.dumps(REV_REG_DEFS), + json.dumps("rev_reg_entries"), + ) + + assert verified is True + assert len(msgs) == 3 + assert "TS_OUT_NRI::18_uuid" in msgs + assert "TS_OUT_NRI::18_id_GE_uuid" in msgs + assert "TS_OUT_NRI::18_busid_GE_uuid" in msgs + + @async_mock.patch("indy.anoncreds.verifier_verify_proof") + async def test_check_pred_names_tamper_pred_value(self, mock_verify): + INDY_PROOF_X = deepcopy(INDY_PROOF_PRED_NAMES) + INDY_PROOF_X["proof"]["proofs"][0]["primary_proof"]["ge_proofs"][0][ + "predicate" + ]["value"] = 0 + with async_mock.patch.object( + IndyLedgerRequestsExecutor, "get_ledger_for_identifier" + ) as mock_get_ledger: + mock_get_ledger.return_value = (None, self.ledger) + (verified, msgs) = await self.verifier.verify_presentation( + deepcopy(INDY_PROOF_REQ_PRED_NAMES), + INDY_PROOF_X, + "schemas", + {"LjgpST2rjsoxYegQDRm7EL:3:CL:18:tag": {"value": {}}}, + REV_REG_DEFS, + "rev_reg_entries", + ) + + mock_verify.assert_not_called() + + assert verified is False + assert len(msgs) == 4 + assert "RMV_RFNT_NRI::18_uuid" in msgs + assert "RMV_RFNT_NRI::18_busid_GE_uuid" in msgs + assert "RMV_RFNT_NRI::18_id_GE_uuid" in msgs + assert ( + "VALUE_ERROR::Timestamp on sub-proof #0 is superfluous vs. requested attribute group 18_uuid" + in msgs + ) + + @async_mock.patch("indy.anoncreds.verifier_verify_proof") + async def test_check_pred_names_tamper_pred_req_attr(self, mock_verify): + INDY_PROOF_REQ_X = deepcopy(INDY_PROOF_REQ_PRED_NAMES) + INDY_PROOF_REQ_X["requested_predicates"]["18_busid_GE_uuid"]["name"] = "dummy" + with async_mock.patch.object( + IndyLedgerRequestsExecutor, "get_ledger_for_identifier" + ) as mock_get_ledger: + mock_get_ledger.return_value = (None, self.ledger) + (verified, msgs) = await self.verifier.verify_presentation( + INDY_PROOF_REQ_X, + INDY_PROOF_PRED_NAMES, + "schemas", + {"LjgpST2rjsoxYegQDRm7EL:3:CL:18:tag": {"value": {}}}, + REV_REG_DEFS, + "rev_reg_entries", + ) + + mock_verify.assert_not_called() + + assert verified is False + assert len(msgs) == 4 + assert "RMV_RFNT_NRI::18_uuid" in msgs + assert "RMV_RFNT_NRI::18_busid_GE_uuid" in msgs + assert "RMV_RFNT_NRI::18_id_GE_uuid" in msgs + assert ( + "VALUE_ERROR::Timestamp on sub-proof #0 is superfluous vs. requested attribute group 18_uuid" + in msgs + ) + + @async_mock.patch("indy.anoncreds.verifier_verify_proof") + async def test_check_pred_names_tamper_attr_groups(self, mock_verify): + INDY_PROOF_X = deepcopy(INDY_PROOF_PRED_NAMES) + INDY_PROOF_X["requested_proof"]["revealed_attr_groups"][ + "x_uuid" + ] = INDY_PROOF_X["requested_proof"]["revealed_attr_groups"].pop("18_uuid") + with async_mock.patch.object( + IndyLedgerRequestsExecutor, "get_ledger_for_identifier" + ) as mock_get_ledger: + mock_get_ledger.return_value = ("test", self.ledger) + (verified, msgs) = await self.verifier.verify_presentation( + deepcopy(INDY_PROOF_REQ_PRED_NAMES), + INDY_PROOF_X, + "schemas", + {"LjgpST2rjsoxYegQDRm7EL:3:CL:18:tag": {"value": {}}}, + REV_REG_DEFS, + "rev_reg_entries", + ) + + mock_verify.assert_not_called() + + assert verified is False + assert len(msgs) == 3 + assert "RMV_RFNT_NRI::18_busid_GE_uuid" in msgs + assert "RMV_RFNT_NRI::18_id_GE_uuid" in msgs + assert "VALUE_ERROR::Missing requested attribute group 18_uuid" in msgs diff --git a/aries_cloudagent/indy/sdk/tests/test_wallet_plugin.py b/aries_cloudagent/indy/sdk/tests/test_wallet_plugin.py new file mode 100644 index 0000000000..d683e7b215 --- /dev/null +++ b/aries_cloudagent/indy/sdk/tests/test_wallet_plugin.py @@ -0,0 +1,129 @@ +import pytest + +from asynctest import mock as async_mock, TestCase as AsyncTestCase + +from ....ledger.base import BaseLedger +from ....wallet.base import BaseWallet +from ....wallet.did_info import DIDInfo + +from .. import wallet_plugin as test_module + + +class TestWalletCrypto(AsyncTestCase): + def setUp(self): + test_module.LOADED = False + + async def test_file_ext(self): + assert test_module.file_ext() + + def test_load_postgres_plugin(self): + storage_config = '{"wallet_scheme":"MultiWalletSingleTable"}' + storage_creds = '{"account":"test"}' + mock_stg_lib = async_mock.MagicMock( + postgresstorage_init=async_mock.MagicMock(return_value=0), + init_storagetype=async_mock.MagicMock(return_value=0), + ) + with async_mock.patch.object( + test_module.cdll, "LoadLibrary", async_mock.Mock() + ) as mock_load: + mock_load.return_value = mock_stg_lib + test_module.load_postgres_plugin(storage_config, storage_creds) + + assert test_module.LOADED + + def test_load_postgres_plugin_init_x_raise(self): + storage_config = '{"wallet_scheme":"MultiWalletSingleTable"}' + storage_creds = '{"account":"test"}' + mock_stg_lib = async_mock.MagicMock( + postgresstorage_init=async_mock.MagicMock(return_value=2) + ) + with async_mock.patch.object( + test_module.cdll, "LoadLibrary", async_mock.Mock() + ) as mock_load: + mock_load.return_value = mock_stg_lib + with self.assertRaises(OSError) as context: + test_module.load_postgres_plugin( + storage_config, storage_creds, raise_exc=True + ) + assert "unable to load postgres" in str(context.exception) + + def test_load_postgres_plugin_init_x_exit(self): + storage_config = '{"wallet_scheme":"MultiWalletSingleTable"}' + storage_creds = '{"account":"test"}' + mock_stg_lib = async_mock.MagicMock( + postgresstorage_init=async_mock.MagicMock(return_value=2) + ) + with async_mock.patch.object( + test_module.cdll, "LoadLibrary", async_mock.Mock() + ) as mock_load: + mock_load.return_value = mock_stg_lib + with self.assertRaises(SystemExit): + test_module.load_postgres_plugin( + storage_config, storage_creds, raise_exc=False + ) + + def test_load_postgres_plugin_config_x_raise(self): + storage_config = '{"wallet_scheme":"MultiWalletSingleTable"}' + storage_creds = '{"account":"test"}' + mock_stg_lib = async_mock.MagicMock( + postgresstorage_init=async_mock.MagicMock(return_value=0), + init_storagetype=async_mock.MagicMock(return_value=2), + ) + with async_mock.patch.object( + test_module.cdll, "LoadLibrary", async_mock.Mock() + ) as mock_load: + mock_load.return_value = mock_stg_lib + with self.assertRaises(OSError) as context: + test_module.load_postgres_plugin( + storage_config, storage_creds, raise_exc=True + ) + assert "unable to configure postgres" in str(context.exception) + + def test_load_postgres_plugin_config_x_exit(self): + storage_config = '{"wallet_scheme":"MultiWalletSingleTable"}' + storage_creds = '{"account":"test"}' + mock_stg_lib = async_mock.MagicMock( + postgresstorage_init=async_mock.MagicMock(return_value=0), + init_storagetype=async_mock.MagicMock(return_value=2), + ) + with async_mock.patch.object( + test_module.cdll, "LoadLibrary", async_mock.Mock() + ) as mock_load: + mock_load.return_value = mock_stg_lib + with self.assertRaises(SystemExit): + test_module.load_postgres_plugin( + storage_config, storage_creds, raise_exc=False + ) + + def test_load_postgres_plugin_bad_json_x_raise(self): + storage_config = '{"wallet_scheme":"MultiWalletSingleTable"}' + storage_creds = '"account":"test"' + mock_stg_lib = async_mock.MagicMock( + postgresstorage_init=async_mock.MagicMock(return_value=0), + init_storagetype=async_mock.MagicMock(return_value=2), + ) + with async_mock.patch.object( + test_module.cdll, "LoadLibrary", async_mock.Mock() + ) as mock_load: + mock_load.return_value = mock_stg_lib + with self.assertRaises(OSError) as context: + test_module.load_postgres_plugin( + storage_config, storage_creds, raise_exc=True + ) + assert "Invalid stringified JSON input" in str(context.exception) + + def test_load_postgres_plugin_bad_json_x_exit(self): + storage_config = '"wallet_scheme":"MultiWalletSingleTable"' + storage_creds = '{"account":"test"}' + mock_stg_lib = async_mock.MagicMock( + postgresstorage_init=async_mock.MagicMock(return_value=0), + init_storagetype=async_mock.MagicMock(return_value=2), + ) + with async_mock.patch.object( + test_module.cdll, "LoadLibrary", async_mock.Mock() + ) as mock_load: + mock_load.return_value = mock_stg_lib + with self.assertRaises(SystemExit): + test_module.load_postgres_plugin( + storage_config, storage_creds, raise_exc=False + ) diff --git a/aries_cloudagent/indy/sdk/util.py b/aries_cloudagent/indy/sdk/util.py new file mode 100644 index 0000000000..549d06e965 --- /dev/null +++ b/aries_cloudagent/indy/sdk/util.py @@ -0,0 +1,29 @@ +"""Indy utilities.""" + +import json + +from pathlib import Path + +import indy.blob_storage + + +async def create_tails_reader(tails_file_path: str) -> int: + """Get a handle for the blob_storage file reader.""" + tails_file_path = Path(tails_file_path) + + if not tails_file_path.exists(): + raise FileNotFoundError("Tails file does not exist.") + + tails_reader_config = json.dumps( + { + "base_dir": str(tails_file_path.parent.absolute()), + "file": str(tails_file_path.name), + } + ) + return await indy.blob_storage.open_reader("default", tails_reader_config) + + +async def create_tails_writer(tails_base_dir: str) -> int: + """Get a handle for the blob_storage file writer.""" + tails_writer_config = json.dumps({"base_dir": tails_base_dir, "uri_pattern": ""}) + return await indy.blob_storage.open_writer("default", tails_writer_config) diff --git a/aries_cloudagent/indy/sdk/verifier.py b/aries_cloudagent/indy/sdk/verifier.py new file mode 100644 index 0000000000..5c67463eed --- /dev/null +++ b/aries_cloudagent/indy/sdk/verifier.py @@ -0,0 +1,88 @@ +"""Indy SDK verifier implementation.""" + +import json +import logging + +import indy.anoncreds +from indy.error import IndyError + +from ...core.profile import Profile + +from ..verifier import IndyVerifier, PresVerifyMsg + +LOGGER = logging.getLogger(__name__) + + +class IndySdkVerifier(IndyVerifier): + """Indy-SDK verifier implementation.""" + + def __init__(self, profile: Profile): + """ + Initialize an IndyVerifier instance. + + Args: + profile: Active Profile instance + + """ + self.profile = profile + + async def verify_presentation( + self, + pres_req, + pres, + schemas, + credential_definitions, + rev_reg_defs, + rev_reg_entries, + ) -> (bool, list): + """ + Verify a presentation. + + Args: + pres_req: Presentation request data + pres: Presentation data + schemas: Schema data + credential_definitions: credential definition data + rev_reg_defs: revocation registry definitions + rev_reg_entries: revocation registry entries + """ + + LOGGER.debug(f">>> received presentation: {pres}") + LOGGER.debug(f">>> for pres_req: {pres_req}") + msgs = [] + try: + msgs += self.non_revoc_intervals(pres_req, pres, credential_definitions) + msgs += await self.check_timestamps( + self.profile, pres_req, pres, rev_reg_defs + ) + msgs += await self.pre_verify(pres_req, pres) + except ValueError as err: + s = str(err) + msgs.append(f"{PresVerifyMsg.PRES_VALUE_ERROR.value}::{s}") + LOGGER.error( + f"Presentation on nonce={pres_req['nonce']} " + f"cannot be validated: {str(err)}" + ) + return (False, msgs) + + LOGGER.debug(f">>> verifying presentation: {pres}") + LOGGER.debug(f">>> for pres_req: {pres_req}") + try: + verified = await indy.anoncreds.verifier_verify_proof( + json.dumps(pres_req), + json.dumps(pres), + json.dumps(schemas), + json.dumps(credential_definitions), + json.dumps(rev_reg_defs), + json.dumps(rev_reg_entries), + ) + except IndyError as err: + s = str(err) + msgs.append(f"{PresVerifyMsg.PRES_VERIFY_ERROR.value}::{s}") + LOGGER.exception( + f"Validation of presentation on nonce={pres_req['nonce']} " + "failed with error" + ) + verified = False + + return (verified, msgs) diff --git a/aries_cloudagent/indy/sdk/wallet_plugin.py b/aries_cloudagent/indy/sdk/wallet_plugin.py new file mode 100644 index 0000000000..8dbdb24775 --- /dev/null +++ b/aries_cloudagent/indy/sdk/wallet_plugin.py @@ -0,0 +1,63 @@ +"""Utility for loading Postgres wallet plug-in.""" + +import logging +import platform +import json +from ctypes import cdll, c_char_p + +EXTENSION = {"darwin": ".dylib", "linux": ".so", "win32": ".dll", "windows": ".dll"} +LOADED = False +LOGGER = logging.getLogger(__name__) + + +def file_ext(): + """Determine file extension based on platform.""" + your_platform = platform.system().lower() + return EXTENSION[your_platform] if (your_platform in EXTENSION) else ".so" + + +def load_postgres_plugin(storage_config, storage_creds, raise_exc=False): + """Load postgres dll and configure postgres wallet.""" + global LOADED, LOGGER + + if not LOADED: + LOGGER.info( + "Checking input postgres storage_config and storage_creds arguments" + ) + try: + json.loads(storage_config) + json.loads(storage_creds) + except json.decoder.JSONDecodeError: + LOGGER.error( + "Invalid stringified JSON input, check storage_config and storage_creds" + ) + if raise_exc: + raise OSError( + "Invalid stringified JSON input, " + "check storage_config and storage_creds" + ) + else: + raise SystemExit(1) + + LOGGER.info("Initializing postgres wallet") + stg_lib = cdll.LoadLibrary("libindystrgpostgres" + file_ext()) + result = stg_lib.postgresstorage_init() + if result != 0: + LOGGER.error("Error unable to load postgres wallet storage: %s", result) + if raise_exc: + raise OSError(f"Error unable to load postgres wallet storage: {result}") + else: + raise SystemExit(1) + if "wallet_scheme" in storage_config: + c_config = c_char_p(storage_config.encode("utf-8")) + c_credentials = c_char_p(storage_creds.encode("utf-8")) + result = stg_lib.init_storagetype(c_config, c_credentials) + if result != 0: + LOGGER.error("Error unable to configure postgres stg: %s", result) + if raise_exc: + raise OSError(f"Error unable to configure postgres stg: {result}") + else: + raise SystemExit(1) + LOADED = True + + LOGGER.info("Success, loaded postgres wallet storage") diff --git a/aries_cloudagent/indy/sdk/wallet_setup.py b/aries_cloudagent/indy/sdk/wallet_setup.py new file mode 100644 index 0000000000..280f7abdc9 --- /dev/null +++ b/aries_cloudagent/indy/sdk/wallet_setup.py @@ -0,0 +1,236 @@ +"""Indy-SDK wallet setup and configuration.""" + +import json +import logging + +from typing import Any, Mapping + +import indy.anoncreds +import indy.did +import indy.crypto +import indy.wallet + +from indy.error import IndyError, ErrorCode + +from ...core.error import ProfileError, ProfileDuplicateError, ProfileNotFoundError +from ...core.profile import Profile + +from .error import IndyErrorHandler +from .wallet_plugin import load_postgres_plugin + +LOGGER = logging.getLogger(__name__) + + +class IndyWalletConfig: + """A helper class for handling Indy-SDK wallet configuration.""" + + DEFAULT_FRESHNESS = False + DEFAULT_KEY = "" + DEFAULT_KEY_DERIVATION = "ARGON2I_MOD" + DEFAULT_STORAGE_TYPE = None + + KEY_DERIVATION_RAW = "RAW" + KEY_DERIVATION_ARGON2I_INT = "ARGON2I_INT" + KEY_DERIVATION_ARGON2I_MOD = "ARGON2I_MOD" + + def __init__(self, config: Mapping[str, Any] = None): + """Initialize an `IndySdkWalletConfig` instance. + + Args: + config: {name, key, seed, did, auto_recreate, auto_remove, + storage_type, storage_config, storage_creds} + + """ + + config = config or {} + self.auto_recreate = config.get("auto_recreate", False) + self.auto_remove = config.get("auto_remove", False) + self.freshness_time = config.get("freshness_time", self.DEFAULT_FRESHNESS) + self.key = config.get("key", self.DEFAULT_KEY) + self.key_derivation_method = ( + config.get("key_derivation_method") or self.DEFAULT_KEY_DERIVATION + ) + # self.rekey = config.get("rekey") + # self.rekey_derivation_method = config.get("rekey_derivation_method") + self.name = config.get("name") or Profile.DEFAULT_NAME + self.storage_type = config.get("storage_type") or self.DEFAULT_STORAGE_TYPE + self.storage_config = config.get("storage_config", None) + self.storage_creds = config.get("storage_creds", None) + + if self.storage_type == "postgres_storage": + load_postgres_plugin(self.storage_config, self.storage_creds) + + @property + def wallet_config(self) -> dict: + """Accessor for the Indy wallet config.""" + ret = { + "id": self.name, + "freshness_time": self.freshness_time, + "storage_type": self.storage_type, + } + if self.storage_config is not None: + ret["storage_config"] = json.loads(self.storage_config) + return ret + + @property + def wallet_access(self) -> dict: + """Accessor the Indy wallet access info.""" + ret = {"key": self.key, "key_derivation_method": self.key_derivation_method} + # if self.rekey: + # ret["rekey"] = self.rekey + # if self.rekey_derivation_method: + # ret["rekey_derivation_method"] = self.rekey_derivation_method + if self.storage_creds is not None: + ret["storage_credentials"] = json.loads(self.storage_creds) + return ret + + async def create_wallet(self) -> "IndyOpenWallet": + """ + Create a new wallet. + + Raises: + ProfileDuplicateError: If there was an existing wallet with the same name + ProfileError: If there was a problem removing the wallet + ProfileError: If there was another libindy error + + """ + if self.auto_recreate: + try: + await self.remove_wallet() + except ProfileNotFoundError: + pass + try: + await indy.wallet.create_wallet( + config=json.dumps(self.wallet_config), + credentials=json.dumps(self.wallet_access), + ) + except IndyError as x_indy: + if x_indy.error_code == ErrorCode.WalletAlreadyExistsError: + raise IndyErrorHandler.wrap_error( + x_indy, + f"Cannot create wallet '{self.name}', already exists", + ProfileDuplicateError, + ) from x_indy + raise IndyErrorHandler.wrap_error( + x_indy, + f"Error creating wallet '{self.name}'", + ProfileError, + ) from x_indy + + try: + return await self.open_wallet(created=True) + except ProfileNotFoundError as err: + raise ProfileError( + f"Wallet '{self.name}' not found after creation" + ) from err + + async def remove_wallet(self): + """ + Remove an existing wallet. + + Raises: + ProfileNotFoundError: If the wallet could not be found + ProfileError: If there was another libindy error + + """ + try: + await indy.wallet.delete_wallet( + config=json.dumps(self.wallet_config), + credentials=json.dumps(self.wallet_access), + ) + except IndyError as x_indy: + if x_indy.error_code == ErrorCode.WalletNotFoundError: + raise IndyErrorHandler.wrap_error( + x_indy, + f"Wallet '{self.name}' not found", + ProfileNotFoundError, + ) from x_indy + raise IndyErrorHandler.wrap_error( + x_indy, f"Error removing wallet '{self.name}'", ProfileError + ) from x_indy + + async def open_wallet(self, created: bool = False) -> "IndyOpenWallet": + """ + Open wallet, removing and/or creating it if so configured. + + Raises: + ProfileError: If wallet not found after creation + ProfileNotFoundError: If the wallet is not found + ProfileError: If the wallet is already open + ProfileError: If there is another libindy error + + """ + handle = None + + while True: + try: + handle = await indy.wallet.open_wallet( + config=json.dumps(self.wallet_config), + credentials=json.dumps(self.wallet_access), + ) + # if self.rekey: + # self.key = self.rekey + # self.rekey = None + # if self.rekey_derivation_method: + # self.key_derivation_method = self.rekey_derivation_method + # self.rekey_derivation_method = None + break + except IndyError as x_indy: + if x_indy.error_code == ErrorCode.WalletNotFoundError: + raise IndyErrorHandler.wrap_error( + x_indy, f"Wallet '{self.name}' not found", ProfileNotFoundError + ) from x_indy + elif x_indy.error_code == ErrorCode.WalletAlreadyOpenedError: + raise IndyErrorHandler.wrap_error( + x_indy, f"Wallet '{self.name}' is already open", ProfileError + ) from x_indy + else: + raise IndyErrorHandler.wrap_error( + x_indy, f"Error opening wallet '{self.name}'", ProfileError + ) from x_indy + + LOGGER.info("Creating master secret...") + try: + master_secret_id = await indy.anoncreds.prover_create_master_secret( + handle, self.name + ) + except IndyError as x_indy: + if x_indy.error_code == ErrorCode.AnoncredsMasterSecretDuplicateNameError: + LOGGER.info("Master secret already exists") + master_secret_id = self.name + else: + raise IndyErrorHandler.wrap_error( + x_indy, f"Wallet '{self.name}' error", ProfileError + ) from x_indy + + return IndyOpenWallet(self, created, handle, master_secret_id) + + +class IndyOpenWallet: + """Handle and metadata for an opened Indy wallet.""" + + def __init__( + self, + config: IndyWalletConfig, + created, + handle, + master_secret_id: str, + ): + """Create a new IndyOpenWallet instance.""" + self.config = config + self.created = created + self.handle = handle + self.master_secret_id = master_secret_id + + @property + def name(self) -> str: + """Accessor for the opened wallet name.""" + return self.config.name + + async def close(self): + """Close previously-opened wallet, removing it if so configured.""" + if self.handle: + await indy.wallet.close_wallet(self.handle) + self.handle = None + if self.config.auto_remove: + await self.config.remove_wallet() diff --git a/aries_cloudagent/indy/tests/__init__.py b/aries_cloudagent/indy/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aries_cloudagent/indy/tests/test_verifier.py b/aries_cloudagent/indy/tests/test_verifier.py new file mode 100644 index 0000000000..90b75b92a6 --- /dev/null +++ b/aries_cloudagent/indy/tests/test_verifier.py @@ -0,0 +1,921 @@ +import pytest + +from copy import deepcopy +from time import time + +from asynctest import TestCase as AsyncTestCase +from asynctest import mock as async_mock + +from ...core.in_memory import InMemoryProfile +from ...ledger.multiple_ledger.ledger_requests_executor import ( + IndyLedgerRequestsExecutor, +) +from ...multitenant.base import BaseMultitenantManager +from ...multitenant.manager import MultitenantManager + +from .. import verifier as test_module +from ..verifier import IndyVerifier + + +INDY_PROOF_REQ_NAME = { + "nonce": "15606741555044336341559", + "name": "proof_req", + "version": "0.0", + "requested_attributes": { + "19_uuid": { + "name": "Preferred Name", + "restrictions": [{"cred_def_id": "LjgpST2rjsoxYegQDRm7EL:3:CL:19:tag"}], + } + }, + "requested_predicates": {}, + "non_revoked": {"from": 1579892963, "to": 1579892963}, +} +INDY_PROOF_NAME = { + "proof": { + "proofs": [ + { + "primary_proof": { + "eq_proof": { + "revealed_attrs": { + "preferredname": "94607763023542937648705576709896212619553924110058781320304650334433495169960" + }, + "a_prime": "...", + "e": "...", + "v": "...", + "m": {"master_secret": "...", "musthave": "..."}, + "m2": "...", + }, + "ge_proofs": [], + }, + "non_revoc_proof": None, + } + ], + "aggregated_proof": {"c_hash": "...", "c_list": [[1, 152, 172, 159]]}, + }, + "requested_proof": { + "revealed_attrs": { + "19_uuid": { + "sub_proof_index": 0, + "raw": "Chicken Hawk", + "encoded": "94607763023542937648705576709896212619553924110058781320304650334433495169960", + } + }, + "self_attested_attrs": {}, + "unrevealed_attrs": {}, + "predicates": {}, + }, + "identifiers": [ + { + "schema_id": "LjgpST2rjsoxYegQDRm7EL:2:non-revo:1579888926.0", + "cred_def_id": "LjgpST2rjsoxYegQDRm7EL:3:CL:19:tag", + "rev_reg_id": "LjgpST2rjsoxYegQDRm7EL:4:LjgpST2rjsoxYegQDRm7EL:3:CL:18:tag:CL_ACCUM:0", + "timestamp": 1579892963, + } + ], +} + +INDY_PROOF_REQ_PRED_NAMES = { + "nonce": "12301197819298309547817", + "name": "proof_req", + "version": "0.0", + "requested_attributes": { + "18_uuid": { + "names": [ + "effectiveDate", + "jurisdictionId", + "endDate", + "legalName", + "orgTypeId", + ], + "restrictions": [{"cred_def_id": "LjgpST2rjsoxYegQDRm7EL:3:CL:18:tag"}], + "non_revoked": {"from": 1579892963, "to": 1579892963}, + } + }, + "requested_predicates": { + "18_id_GE_uuid": { + "name": "id", + "p_type": ">=", + "p_value": 4, + "restrictions": [{"cred_def_id": "LjgpST2rjsoxYegQDRm7EL:3:CL:18:tag"}], + "non_revoked": {"from": 1579892963, "to": 1579892963}, + }, + "18_busid_GE_uuid": { + "name": "busId", + "p_type": ">=", + "p_value": 11198760, + "restrictions": [{"cred_def_id": "LjgpST2rjsoxYegQDRm7EL:3:CL:18:tag"}], + "non_revoked": {"from": 1579892963, "to": 1579892963}, + }, + }, +} + +INDY_PROOF_PRED_NAMES = { + "proof": { + "proofs": [ + { + "primary_proof": { + "eq_proof": { + "revealed_attrs": { + "effectivedate": "29898645652691622994103043707423726726370719600737126045061047957925549204159", + "enddate": "102987336249554097029535212322581322789799900648198034993379397001115665086549", + "jurisdictionid": "1", + "legalname": "106000828387368179355563788886235175190145445419967766011746391966411797095112", + "orgtypeid": "2", + }, + "a_prime": "15004053730967415956444780426929245426212215338862984979494015601906315582840747306504594441147441231491693951307278868750626954115009843921712832446544313561614118651520859494434080523236571958503756731624044004577892061145780190353067193496632483652558392939182437813999901132281095751156851493821923092362225145694407463842363472935374563198989094026343235461171230866928987229440390088485243428084237480758852248249770191814659757536925909483623366615812343227994433513635227834136882757394235805747686707186194818800509367263735891033464810268941516104197138946893490285348940539273855011764023467736767154303840", + "e": "337235637061060569047727820825037317712308782222370290484075504679799877011498224496826887984625822621748120654975531604507028064312710", + "v": "1404574530639210172781710601270953478414552186112985513475784201805119858770941821816370201652610496512142438596496007803868074196519046400754620766301997215969127187833134416898687892635798644651536667962849945968608408680347359937747715599800353850666709655353571249823190377976481837350280859973291095846106817174217510384400072134061086282647508604512946800721425580606901739211324799734725830882957974114794011791236006123406974194631084620463349145771704097181067806553409326685136263002734388842031423620455266365851581883497063570426034222596154597920580362387253753317413050267993785894175555001456331223234400596625730555935567545822248399326429854362227165802672227905967242505077485029657064067770641969647406371744932313880132835781524174868335472062214928455106355639700336515509695339440337721239602256387991397127509846614577908429409389332146746885470613002111095896313068354016587589778644661193149185049", + "m": { + "master_secret": "268741899404098839327031223989228937242803085201179726908056281850709820406283972250249379228789368664433583241086441517910928033266925485611584652328638784395957058632060633630", + "busid": "2687197004064133543257369626470144380098036289489284489320086515026620206692616047425976133587124290887441908383692364439260071404270430528078491104384060203570606253676528361400", + "id": "6686713960986576137959547581149682718587656100042127047852747024276212127252400140409890726173570723819441146289878657604374865560165489933600436341950054222778208816245032311193", + }, + "m2": "1180732317592917288409508571561928260151012766032216949553655321777067495114084046539503538100319204468787213948625648828272873800122130063408401311370987", + }, + "ge_proofs": [ + { + "u": { + "0": "15775609194986735920510151800942995799222803216082415810148803816296803079801357496664353906579826995829149362968465835795491271435248746459334118965204125314594582971550392227954", + "2": "5303150256152520023495176881750201750170184894234097909710215547442554470805609846521764595898911334528530696240025838754931022084938196723161868181531727845300439592437899863887", + "3": "3356711078459696620189646681109895593397921422760359051406583045001333345458592898545852513866307624143916692556089833035405496562577023756005223378326300905996972689863856066875", + "1": "9999991890173781186974768504758157527548652482914116775165195164578745484991479122468109103928320060297494255214338396428491092606606051561499468708339979065194763516537003502062", + }, + "r": { + "DELTA": "1435090146724677611480872211988213747514582597135551797832629955760022689079479873681839403744643599039883834204615937515288097736927712499250203649611222666450687692819628191366070914555251320872315378202337414304735555708434851449005494065128333408295370378734399236857073675785782330461793283646613324794741612075132251003819809779185772616720952264615331933630593162857145006881047266047864525898689246790061753791575361871922643386721142202508891103428155725164118848489256450446385140752308548079412012057535799088600334139468432242004848224338715577616491890083382988533414746224157485737168172255373805848589505587117269746736884630803388115258573174965628402748672653106950672620945656786479587908733067646954", + "2": "1256008392268065460119207279471943768595068414860014149178278435399371400930962253759162888062020269227529923329167742931240473191566641468995735758696802138379814852469042293401437137956018945170092817785505583108837356735852654194156319248319729732466320634812831259869290012647072233952795236462156213752008019954481267917886292498492055846838619027167304191382208021540244250507570410888356448310603088364351895116324030484480320862223729665151544010778941061938440283026451178172286282191179070116776836046514823559685428369094109958537683453915206656040875788707049636999992215238431149080323327641760705766913474027428111260788981734182250598573031877786769378931547452684486997457718460021235476398326984192784", + "0": "1106819115015372998825208031668263762670044285179584053573615157030968633235403572251376628759852167093842839880087972608252236859761641404161906797947710722723839146345785722305071566665021165225324459369096631275993154775947819333058721055644040212303789659749985973470385218248603826878093609862767077416104661216163222886987607841769251824856950498177308519655255200045046063857789306026581362754683975573850037114774186895901788964351934533171525025276070855270632786783019588176816118329221122549398793872640055312900842112041891936494042853798319986365192512964078607266631918748545903216736690057842950922926661694759259530843862322858400156976838479950950178486526234308178957984785053903260967594398611911474", + "3": "1344309321242892215222847471501532826517184846819833677474602495849657271930678291855112591971466462816524573183554788643533109793416521709602069842696124889558288092186793062177006244758779556603409762571362221142413760629539533275654542467194539359678435299002566931998816165917234259226849828723125451685169672272552524344813036153633311318760938874320338280443847065712732394378892985736654998112090834297537844732478643713076998558297751199030671616253345870616092528684635775411928128373368327191277066131632614473951005152162823879892345970535505519113833062530738837915987508410926372810518540478552946670006272356196419957933718303344632112441115930033837912179851905872564389256853587645059720488720795906498", + "1": "601693817301763663113031272722721908754633550776510238958619960119672962844730314111437951375084589705366750107667669458320527173785853103929038523863706300574327283273485302578112396814149563941340189390051835244496748959403476105143355455812570759887645896592114448469303958006046663589996470308366068555479184906610439541373120510006128200782324694975090482529033281708168823833732457689747330091963586305323138559502300486975246644545238909598413498252470653544977963083975726047754021026165970401681664501179243533611966433308438886961268871140737772352678991735861225177227793364352974323624694500485545573621034350559474030565509027433457718466600471361048730018443642651540442802817989514889987171548775560085", + }, + "mj": "2687197004064133543257369626470144380098036289489284489320086515026620206692616047425976133587124290887441908383692364439260071404270430528078491104384060203570606253676528361400", + "alpha": "55264634475788812054149982413198771839810724235465324658821557285735947681415835295178267002738090787902834904063083682990582592095393028970773939852521059447360650213986737569312363077820486616943853008592650537183003498185887824618357246364458614494253289122927160626742649252943244636915456680482390825080294565093068093917997001255757200832353046300646785756652682640188703523223073037006585218839054980180609464837830370157522462983934135435603408143309318659202555550473599548567996557919032937165600303958449173855781262863161799425917680286809410314205550551542955745937751254083650878398344461109371177805333303453760504594222290495116260958547048583654306199387054245295488649024179114894686831993370968945510894767150406222332165620064150891563554498413420757277508788138394747656372783710437243804659113648361274361422790365575", + "t": { + "2": "1276353167840913477021397624773394332173592088650367702185572394040398533199538101776458275797662881371280361310311170677242402214354355702620614537036611922064060504606618126681639882263139365680565350790281701009940301284340534766480451762902788628875609130151618956111512660983755135355570760793108220842022869639781026918247205511538713530652099730605791686827103126406846076633375908411453922078354225032716111673736810973402770388177401531928271370790938081733309345905963052715943136682338494175330354955277424030755355371412956250746882945100461786601740318616758180741835591171045104436982446340050589105952", + "0": "52506109491039096251755479392960889070840776962363540274456217953760113102006029814040519995494713986268145627084927516727099691151450378385140332116480118436738261593744184296007314732823898043080011956933010369575980799348117283597824162615912372823633177749168952698401203464607973674241038357379577293158404669765882906589960120865518413803711729942613061301420107178603192154873722316947550106277771120767826035047479123749931790881679576800340417944013614994751361795012191068369383577242249201927422484806926120532089036692818076818060938822432774203557319821915034796962936855918437128832683302834778450852076", + "1": "113031374658594175812052384858113115052077482873081996361152721528334589441352531310470368095073157716273853401381658707580502108484382463859531044307244944300120928991532655473230562771713806228238940140492981669914382036157400059197253018428984542349187927786210979478008036674432605219414300881116700073904513558719492127462395417843765324361843076852973933175787635618464392198807598044268223652564648024618437362752148593227485835178720349721798423100634521510710239416375840314170338898512726956877281625226003452828033987655579773273571285524048285234475184043290899568731903112287738739915600509899766360789888", + "DELTA": "48234611140928682288937615809872962358698394776719271528059766394227502012090856649758227578113306604028516575292703546202775777621049060595611852517094547384541819122623967215175704296901562660240718967260151010609870475975072516070346770954330313963878747194405855012585768501635077016535846206257741952202337055842434195875166686634891739392750890861333875772954056854354284061793365725202163447856793288398072711194949704852318180275797984445814279136152858759907525062790250675184786142234427994860090376938644764493873560454829155051260697226196758890394411645758956396137763703934929680277278644873416013261035", + "3": "89994605437763910628730772379416923861874648327020237340785010128698483324987645925227420742287632948945347297152300219419713493590120999381541274609870183955909628256613851122899039933589797230083354701292272442523280565440597314568786864750837443422276701528731625877274094155541825495114060437788769205202442723879088098866185978654728309516302335284177924161235100925296934812127598754913984676011716654143603885735997160890946409226842054727795290304131313120189570773196857529159798597569189742987994905034522112705638567954037460125246215182613760404547369876267284411245030884496328403051974209422359756153509", + }, + "predicate": { + "attr_name": "busid", + "p_type": "GE", + "value": 11198760, + }, + }, + { + "u": { + "0": "13639548796026429922431564475630909149287414026758460721805236736313279517016438050089911517098811596997747189614439260518531845477684148307804856579405503329745365642794423965550", + "2": "12692415150154152887167590190910159618471206042982658652940787170770193806407265717354418163057121876574358366510055892372348735991661901637525227498965237677355250159501068181772", + "3": "6699159556719214469836363462599679663866420825429540116943002714507804742697411533141864346616123740789790632843719915716457061440487115732563925309886301301835201778554620543295", + "1": "2018654799729593932888298230804022878883145101317651811950082851492082577094184498971399238402895197739207931768086301073280634251050932415705600476284738694155135236800581664160", + }, + "r": { + "1": "825587756964975640126314737718300012891046538726331178577448524710910340957817679849290109848304786342311186386453239759474660538454793939540876256076287017677140704068118361949660090673111340635478762304690817532764517905140299716866605223450803768338360729151901747687349983483402342999368967231581939563361347289212973086454185400770130710116840233323953976914342262402301362679497329671787598650893202541829399630505463177921655009726556920408538662140155815031458475909120161960047235187953148398737965729023268444789967620657212914775071615366971436269789139928904779054710447116218434690464549160131819794059427689273427325814904354192089075836597740878803445045080385629565176143354201573860707045668850877586", + "2": "1466408189748340763973829793343949568330918709265623621614464341218317503955515434953266875378586538446326464353600075579788794127665478299651259465473747112701101990004860122720151191106445704432013015062973865716673386400413561687311954374930156679604666267815298214479078026652043482916898087471155683856282470644588563159648375551108786970597383143516158031628710096807215305878905007543811401502472821013567629888746492557864905681554913361277548019219082051265255078152509205293776781790132507115787621452248689332496610099725566623311760857590035073594921664074567131690599897210005475078142722295326868452002437292574903183037228401231409631285848202575278151773369676950274790626198680132560950102001994557758", + "0": "993248502537248262082444202395290853332499246354083708269674970707520839045168624341335318664418224639164402187209309139427257892643191846187663592057257899679944076599283980872521437340751206357777926871742796186382563827967273141200749480590415594087209691507734426984052841712131263160951495974745152392404724577427973267669378931113495076274617344076060846279028767371296979484895771867209047720463195305161885422275388748188299814182891315332800557749699941587327916028930365349641271736635219800975554147836564077611147631789530042925759823398087582121686407890628257624663383236878047170688254415445440912626941967028065807021170264150964938678824504194752040131898249057197187446968567390619785928296680096859", + "3": "353677912339120670248802964352055631737613331947764251954000578577314223482877266750851861467829550374246392637478716468616296688578414836737374015352059254057436572686513161681724599053168679581126352074962010335889993562619355121275432902043064229165956511160994192882167562213269670332262473472293819501037932879123080023576285854568501212240875918139761976977842939660466373041805369493971290555885442554468124891943099059169515428968196495673746803133324864149723509564523971808556630671471618581233229134929554792186889060256901637092067130348403992303346483664985586122149628146304160243882639275298266216270358565584574585823864941692911554602002331492551293859949912337984877479524597804956696499812250631744", + "DELTA": "725960022886687948013207416539699149371621853290822104811918058808196468403337509381122781137942343897440199450987104988666229964851227549448628470704889721866971126265999067769808855341632931627785927114398786533559660381398895352266657934136549351825103362166280268159652759301507640976500533521688660251972577237532256300306442315564311264115224457865178259661593100327194825492692234619818096596609477148829377559407992257373097100180145505767561403356284282388735420784241021016181364636135275395790815788682767997871662899508826815736302921531147145381730507095314577476550947092200539059112480501048978059997520366967856033897452966490827003353334313372398949710717623991939354590550708881302450618430658953556", + }, + "mj": "6686713960986576137959547581149682718587656100042127047852747024276212127252400140409890726173570723819441146289878657604374865560165489933600436341950054222778208816245032311193", + "alpha": "54312418620368017823413444392364697511954099195960921763340447211826963863335156427497199363801396644023918819073111783414569539781613083737557451917064836287105910850065875966424715696905777194811070071499968289670886194094119693198719726955718469770568556811780821894560508716495412081426259363632509089906620904790770113618876886851367553577555822830824722182497761114967889600421573310792308390968429341290356015872285765321156360499004114406293720515635636721256956836801168192621092752489119545742530767529595705696014856308531466145146269599634259697543058622520958051728230537251854292098956994695268415292349999637984082162556184322623578612708830627477718675001902228134597558345283147625462346943667586065769392740787755841399970302076200764539143397370091692013055692886714129148712005056929884477612627289722508451690081998890", + "t": { + "3": "76842650074027971332631982512373611181628371639695357946107030911055453488768447213460618917725534086368162318588252003797289252473279448248400376609193928062810022884201102892017821282461806593568305060473753735848560048445524907113838106958747793434918052694775405184619214354190540002998204225798499364075579094271521191419027986291013493577021803670203051346914082929873231509819450163988354047777312127725561922611471445963909565688013793926876707562644935391518355932605047591545917637465241017629839541260483606708345518662351776889719949822005165906622964213143757683950646046295114922019124075069329268061942", + "2": "104991045405164906316724339229643785709360971949973916361929774804163421784479300621496063132861029493850348596359070365652827572699577454378465299784873962729586537933990712981855548459986825452865420618489151243413027040820258308949176618728507177438646401022030966936494703173837070422031040550750643315987178063356959004909489540688791639398005266908038895531691252968451136025538449648159989963830846794193607106472742567850015960071634812903985081979755017126350806404047244177458032873066418448813920685609285163826032405474833353441325867090653794998832828049943461795570528006274431422907140560130037296666626", + "0": "93372553202246858510371387492221683266873274595585378473760313800346458391438909787465170333251843314544241604938410847073082151810448655558028921073676767770394665021417900636520680814177493616534162641758512946743051333557436759523671912141418810442158225543010238061117969558203880853763255647243160765086932831295304550412607848190595598510980669944139696363322475177492264636536910201776020324858798972778323663795303339939472573927415127166116444898790357846635883222746031584554927383535016321617425087697872601850303134185636960112124926520878185699975818343081756286170638877967660840814776000077787223928056", + "1": "88156501142569212422367853754801651086852287000049991938144173063936879655987557042149874374683234911554850776079721311154420204826376746982087019508277766132575858575556680538019849786965963718649479179859820106973881788608463705644074554956818391872137271784803047333543321479251515998725336896820211102747123583803854741248907240437683401575881169746704849524328003061107258995982062254548684849294595639491104266371155934951313704136302996039897528196270331875472554549327417349243990461246127383357748906616773662459665620147625796186736530089927957522542298814250937114283836911153790542409683746775259226224961", + "DELTA": "6178997688515360528852083990605883033892934661031543684879979804577432521872124008044788245406591933749401429548633356472853716766388636618335206416158216292785839570827245139150787585027801572977051847786797012358936548986405917266204321163760568135873831346087680040040301251630530064206552793273933549993844498438010903850013120770715837075880286264742500598494248510064600863411203212869270221192303957773402376357672080257393369434247825409313396018869267942811592657266119556377402842108726474978400793026037873416208879964428023321485607453655856252140587803891157033568210852205447175844430607889546700526279", + }, + "predicate": { + "attr_name": "id", + "p_type": "GE", + "value": 4, + }, + }, + ], + }, + "non_revoc_proof": { + "x_list": { + "rho": "12B28F49BF5F2CDA105B904CD594EB5B5F558025CDDB7D0F3057D19830F81694", + "r": "010F2B872DC4BECAE085D9FA1FB98184C3E00181A678F2B256140482B4DEDFCE", + "r_prime": "1AAF1AB0071B64FE22AC42219962B9ABA02147B09DFDC4C1FD088E6706D312FC", + "r_prime_prime": "1630D0800ADE33816BCA96EE89EC1E312BE80220510FAFAAC52BED567B187017", + "r_prime_prime_prime": "14D06B2F7B369880821DAAFD40D74FE3B495EE3A7CB7E937FDC4E9707330F301", + "o": "147F16718A0CCB2EC137ECA3952C801FB2281B06CB4ADC24092CE31CA2EAC5AD", + "o_prime": "14F4668810341353E0C25485C4F3CF459BCB69DD74FF73376A23ACAA550E09C5", + "m": "0EAC754EE9AC81C495AC3BB302511034965965AF337BC4F43850A1636E70314E", + "m_prime": "07CA764055E9920E7C4417C3A63BF610C145F25D618A41DAC785210D7320F0EF", + "t": "199D84A1133FB103E4E77CC6F917A12355AD4A29DCCC24999A2C67EBD62B5306", + "t_prime": "07154F39841E3D75E1E07179875B94D655E3BDD4A349E0BBAA43422CC527AACB", + "m2": "0D4380FF8ACDC21611BC8AB83127950441DA42A49A347BEC6F32044F033D3017", + "s": "0A65AE9D0C0D4CDAA5D4EECB48BC6DFD2279BD2C040AC0D9011918A9E0A7A866", + "c": "0ABFC02DDF76995C48CADEE8447665EB933446FEC42E7074FB11720E141CFC07", + }, + "c_list": { + "e": "6 418D8713ED93CD8C065EA42D110C581C2CE07A58771077B1C2016E53AA2E7461 4 2032A4917D0877B9723CDCD82B32AC96C534B0CAA5ED2EE3FFD605214511CB1F 4 0D8E5DA074651A0DE91F86F663F071EA4D4CD4CBA438F7A4D182C8D23D01B485", + "d": "6 37635F35298D224C0E3F01EB06DC8AC1D8A7E048027162077E0204801F22FF94 4 1E64440E13B08BD249B5C35B637C70BDA471926F5F3896400ED25EDA4678B73D 4 3A5BB704B473894CD54C91D1D159A7BD8FA8092545F93D1BC195D52D3EC96EDE", + "a": "6 6000DC780B9D7C71575A328DE2BACB78A2737E9C1CE64BC8BCE98BD8486EAAB4 4 39555F38DB15EC820DA3A7A61820F831A003D414D4A0EF60D1D37ABD8B5E1070 4 25FBA1AD320F02D9082118E978B4FE261280951BCE1FED15F65771AE04F8E270", + "g": "6 5D293948EF43989ACBB8262B8C7F10A551AD71190D70B9AAA62943C4FE6A4C42 4 2B3E1ED0A00163DCA9AD9B69DDA124290CF8F580F60595B5E9D506A3C0D9A903 4 29C2B6F7AD7F7B223FC40BD9C1147FCE831B26ACB477855C9D3EABD7B0341342", + "w": "21 1371C921AE2377A1CD9F0D3E863B09487B6DFC0DC5F2BA32133E4F5EF2ACA5641 21 10B84BA9167755980B1DCD97AB698D56E3C9CDCBE7A85F0776A2C96B3BE9519BE 6 6676ADACEC607381F87211DAE4DE6A630B74FAF580DBC383D8450C7852BC09C4 4 379C9A4FF46DEBF21223823D5B2323F7A56A394157E94DB95914A9E5BB27FAEC 6 7121D621C85D9BA22371A0862909FF25198F0EF690207AEE3910FB0E0A7A4F62 4 1C052A0276360F0D8AEBA71BD65ECB233FFDB700F031EA03146CF00BC2F2D5B6", + "s": "21 1272F477F5A0F83CCB316DA088F6A6A12C131D0DC9BC699023F534624B8EE255A 21 13816855011465BE2E8972F52EE4692873A763513A764BD92B8B7CBBBAA27D7E8 6 7B190F599B5F0EA53802135BBD655B080743FE60CC22329F69770D6B765F0AAA 4 2AAA191CA59348C6A920BD1D1AE37A7C96F424B6D8E921B54EA5C1C7C56297AA 6 80254CA5DFBAD3C39BC757534922FBD0846AB86500D5D168109EB6B8A9D2BE33 4 1CC93B3769A7BE2AF52CCE391D2BB57F9D907F530038EF84B3EC4AB54D62D872", + "u": "21 11E538813B74EFC8676EF5AC87AA05A0FF58913B7C68E264FCF5ED0D57F6BC781 21 12EE7BE65E15CF4C500E2D92DB02670FBD8B51C6BD0B35AE139E9CE9658B15CC2 6 856B3C0C152F75A449AD73DFAD7DFD87A99AAA606E3D8E392D765E3C987D7B47 4 34245F01BD7C4144DBEBE7AB35303BF02FB5717EC6B080BC9C2C930D929D4ED7 6 8113F127D8762B174DCB32AEE370297BF7CFCCF797510B53D53564AEC9105909 4 3B2434AD9AB4E7ABA7125800D14470A098AE04FA523CB60A6FFF62D371B95E13", + }, + }, + } + ], + "aggregated_proof": { + "c_hash": "37672016063516849654668323232510746418703126727195560560658262517075578769045", + "c_list": [ + [4, 0, 0, 0, 0, 0], + [4, 17, 153, 0, 0, 0, 0], + [4, 0, 0, 0, 0, 0, 0, 0], + [4, 1, 134, 126, 0, 0, 0, 0], + [10, 250, 248, 125, 158, 54, 165, 91, 59, 1], + [4, 167, 169, 22, 44], + [31, 254, 53], + [118, 218, 1, 27, 51, 96], + [1, 254, 120, 236], + [3, 127, 97, 134, 148, 32, 128], + [10, 124, 191, 32], + [32, 59, 96, 254, 165], + [195, 171, 64, 72, 40, 235], + [2, 175, 185, 172, 248], + [2, 152, 166, 185, 65], + [3, 63, 176, 24, 2], + [2, 96, 182, 196, 220, 182, 246], + [48, 242, 116, 58, 18, 199], + ], + }, + }, + "requested_proof": { + "revealed_attrs": {}, + "revealed_attr_groups": { + "18_uuid": { + "sub_proof_index": 0, + "values": { + "effectiveDate": { + "raw": "2018-01-01", + "encoded": "29898645652691622994103043707423726726370719600737126045061047957925549204159", + }, + "endDate": { + "raw": "", + "encoded": "102987336249554097029535212322581322789799900648198034993379397001115665086549", + }, + "jurisdictionId": {"raw": "1", "encoded": "1"}, + "legalName": { + "raw": "Flan Nebula", + "encoded": "106000828387368179355563788886235175190145445419967766011746391966411797095112", + }, + "orgTypeId": {"raw": "2", "encoded": "2"}, + }, + } + }, + "self_attested_attrs": {}, + "unrevealed_attrs": {}, + "predicates": { + "18_busid_GE_uuid": {"sub_proof_index": 0}, + "18_id_GE_uuid": {"sub_proof_index": 0}, + }, + }, + "identifiers": [ + { + "schema_id": "LjgpST2rjsoxYegQDRm7EL:2:bc-reg:1.0", + "cred_def_id": "LjgpST2rjsoxYegQDRm7EL:3:CL:18:tag", + "rev_reg_id": "LjgpST2rjsoxYegQDRm7EL:4:LjgpST2rjsoxYegQDRm7EL:3:CL:18:tag:CL_ACCUM:0", + "timestamp": 1579892963, + } + ], +} + +REV_REG_DEFS = { + "LjgpST2rjsoxYegQDRm7EL:4:LjgpST2rjsoxYegQDRm7EL:3:CL:18:tag:CL_ACCUM:0": { + "txnTime": 1500000000 + } +} + + +class MockVerifier(IndyVerifier): + async def verify_presentation( + self, + pres_req, + pres, + schemas, + credential_definitions, + rev_reg_defs, + rev_reg_entries, + ) -> bool: + raise NotImplementedError() + + +@pytest.mark.indy +class TestIndySdkVerifier(AsyncTestCase): + def setUp(self): + self.ledger = async_mock.MagicMock( + get_credential_definition=async_mock.CoroutineMock( + return_value={ + "...": "...", + "value": { + "revocation": { + "g": "1 ...", + "g_dash": "1 ...", + "h": "1 ...", + "h0": "1 ...", + "h1": "1 ...", + "h2": "1 ...", + "htilde": "1 ...", + "h_cap": "1 ...", + "u": "1 ...", + "pk": "1 ...", + "y": "1 ...", + } + }, + } + ) + ) + self.verifier = MockVerifier() + + async def test_check_timestamps(self): + # multitenant + mock_profile = InMemoryProfile.test_profile() + context = mock_profile.context + context.injector.bind_instance( + IndyLedgerRequestsExecutor, + IndyLedgerRequestsExecutor(mock_profile), + ) + context.injector.bind_instance( + BaseMultitenantManager, + async_mock.MagicMock(MultitenantManager, autospec=True), + ) + with async_mock.patch.object( + IndyLedgerRequestsExecutor, "get_ledger_for_identifier" + ) as mock_get_ledger: + mock_get_ledger.return_value = (None, self.ledger) + await self.verifier.check_timestamps( + mock_profile, + INDY_PROOF_REQ_NAME, + INDY_PROOF_NAME, + REV_REG_DEFS, + ) + + # all clear, with timestamps + mock_profile = InMemoryProfile.test_profile() + context = mock_profile.context + context.injector.bind_instance( + IndyLedgerRequestsExecutor, IndyLedgerRequestsExecutor(mock_profile) + ) + with async_mock.patch.object( + IndyLedgerRequestsExecutor, "get_ledger_for_identifier" + ) as mock_get_ledger: + mock_get_ledger.return_value = (None, self.ledger) + await self.verifier.check_timestamps( + mock_profile, + INDY_PROOF_REQ_NAME, + INDY_PROOF_NAME, + REV_REG_DEFS, + ) + + # timestamp for irrevocable credential + with async_mock.patch.object( + IndyLedgerRequestsExecutor, "get_ledger_for_identifier" + ) as mock_get_ledger: + mock_get_ledger.return_value = ( + None, + async_mock.MagicMock( + get_credential_definition=async_mock.CoroutineMock( + return_value={ + "...": "...", + "value": {"no": "revocation"}, + } + ) + ), + ) + with self.assertRaises(ValueError) as context: + await self.verifier.check_timestamps( + mock_profile, + INDY_PROOF_REQ_NAME, + INDY_PROOF_NAME, + REV_REG_DEFS, + ) + assert "Timestamp in presentation identifier #" in str(context.exception) + + # all clear, no timestamps + with async_mock.patch.object( + IndyLedgerRequestsExecutor, "get_ledger_for_identifier" + ) as mock_get_ledger: + mock_get_ledger.return_value = (None, self.ledger) + proof_x = deepcopy(INDY_PROOF_NAME) + proof_x["identifiers"][0]["timestamp"] = None + proof_x["identifiers"][0]["rev_reg_id"] = None + proof_req_x = deepcopy(INDY_PROOF_REQ_NAME) + proof_req_x.pop("non_revoked") + await self.verifier.check_timestamps( + mock_profile, + proof_req_x, + proof_x, + REV_REG_DEFS, + ) + + # timestamp in the future + proof_req_x = deepcopy(INDY_PROOF_REQ_NAME) + proof_x = deepcopy(INDY_PROOF_NAME) + proof_x["identifiers"][0]["timestamp"] = int(time()) + 3600 + with self.assertRaises(ValueError) as context: + await self.verifier.check_timestamps( + mock_profile, + proof_req_x, + proof_x, + REV_REG_DEFS, + ) + assert "in the future" in str(context.exception) + + # timestamp in the distant past + proof_x["identifiers"][0]["timestamp"] = 1234567890 + with self.assertRaises(ValueError) as context: + await self.verifier.check_timestamps( + mock_profile, + proof_req_x, + proof_x, + REV_REG_DEFS, + ) + assert "predates rev reg" in str(context.exception) + + # timestamp otherwise outside non-revocation interval: log and continue + proof_req_x = deepcopy(INDY_PROOF_REQ_NAME) + proof_req_x["non_revoked"] = {"from": 1600000000, "to": 1600001000} + proof_x["identifiers"][0]["timestamp"] = 1579890000 + with async_mock.patch.object( + test_module, "LOGGER", async_mock.MagicMock() + ) as mock_logger, async_mock.patch.object( + IndyLedgerRequestsExecutor, "get_ledger_for_identifier" + ) as mock_get_ledger: + mock_get_ledger.return_value = (None, self.ledger) + pre_logger_calls = mock_logger.info.call_count + await self.verifier.check_timestamps( + mock_profile, + proof_req_x, + proof_x, + REV_REG_DEFS, + ) + assert mock_logger.info.call_count == pre_logger_calls + 1 + + # superfluous timestamp + proof_req_x = deepcopy(INDY_PROOF_REQ_NAME) + proof_x = deepcopy(INDY_PROOF_NAME) + proof_req_x.pop("non_revoked") + with async_mock.patch.object( + IndyLedgerRequestsExecutor, "get_ledger_for_identifier" + ) as mock_get_ledger: + mock_get_ledger.return_value = (None, self.ledger) + with self.assertRaises(ValueError) as context: + await self.verifier.check_timestamps( + mock_profile, + proof_req_x, + proof_x, + REV_REG_DEFS, + ) + assert "superfluous" in str(context.exception) + # missing revealed attr + proof_req_x = deepcopy(INDY_PROOF_REQ_NAME) + proof_x = deepcopy(INDY_PROOF_NAME) + proof_x["requested_proof"]["revealed_attrs"] = {} + with self.assertRaises(ValueError) as context: + await self.verifier.check_timestamps( + mock_profile, + proof_req_x, + proof_x, + REV_REG_DEFS, + ) + assert "Presentation attributes mismatch requested" in str( + context.exception + ) + + # all clear, attribute group ('names') + await self.verifier.check_timestamps( + mock_profile, + INDY_PROOF_REQ_PRED_NAMES, + INDY_PROOF_PRED_NAMES, + REV_REG_DEFS, + ) + + # missing revealed attr groups + proof_x = deepcopy(INDY_PROOF_PRED_NAMES) + proof_x["requested_proof"].pop("revealed_attr_groups") + with self.assertRaises(ValueError) as context: + await self.verifier.check_timestamps( + mock_profile, + INDY_PROOF_REQ_PRED_NAMES, + proof_x, + REV_REG_DEFS, + ) + assert "Missing requested attribute group" in str(context.exception) + + # superfluous timestamp, attr group + proof_x = deepcopy(INDY_PROOF_PRED_NAMES) + proof_req_x = deepcopy(INDY_PROOF_REQ_PRED_NAMES) + proof_req_x["requested_attributes"]["18_uuid"].pop("non_revoked") + proof_req_x["requested_predicates"]["18_id_GE_uuid"].pop("non_revoked") + proof_req_x["requested_predicates"]["18_busid_GE_uuid"].pop("non_revoked") + with self.assertRaises(ValueError) as context: + await self.verifier.check_timestamps( + mock_profile, + proof_req_x, + proof_x, + REV_REG_DEFS, + ) + assert "is superfluous vs. requested" in str(context.exception) + + # superfluous timestamp, predicates + proof_x = deepcopy(INDY_PROOF_PRED_NAMES) + proof_req_x = deepcopy(INDY_PROOF_REQ_PRED_NAMES) + proof_req_x["requested_predicates"]["18_id_GE_uuid"].pop("non_revoked") + proof_req_x["requested_predicates"]["18_busid_GE_uuid"].pop("non_revoked") + with self.assertRaises(ValueError) as context: + await self.verifier.check_timestamps( + mock_profile, + proof_req_x, + proof_x, + REV_REG_DEFS, + ) + assert "is superfluous vs. requested predicate" in str(context.exception) + + # mismatched predicates and requested_predicates + proof_x = deepcopy(INDY_PROOF_PRED_NAMES) + proof_req_x = deepcopy(INDY_PROOF_REQ_PRED_NAMES) + proof_x["requested_proof"]["predicates"] = {} + with self.assertRaises(ValueError) as context: + await self.verifier.check_timestamps( + mock_profile, + proof_req_x, + proof_x, + REV_REG_DEFS, + ) + assert "predicates mismatch requested predicate" in str(context.exception) + + async def test_non_revoc_intervals(self): + big_pres_req = { + "nonce": "12301197819298309547817", + "name": "proof_req", + "version": "0.0", + "requested_attributes": { + "17_uuid": { + "name": ["favouriteDrink"], + "restrictions": [ + {"cred_def_id": "LjgpST2rjsoxYegQDRm7EL:3:CL:17:tag"} + ], + "non_revoked": {"from": 1579892963, "to": 1579892963}, + }, + "18_uuid": { + "names": [ + "effectiveDate", + "jurisdictionId", + "endDate", + "legalName", + "orgTypeId", + ], + "restrictions": [ + {"cred_def_id": "LjgpST2rjsoxYegQDRm7EL:3:CL:18:tag"} + ], + "non_revoked": {"from": 1579892963, "to": 1579892963}, + }, + }, + "requested_predicates": { + "18_id_GE_uuid": { + "name": "id", + "p_type": ">=", + "p_value": 4, + "restrictions": [ + {"cred_def_id": "LjgpST2rjsoxYegQDRm7EL:3:CL:18:tag"} + ], + "non_revoked": {"from": 1579892963, "to": 1579892963}, + }, + "18_busid_GE_uuid": { + "name": "busId", + "p_type": ">=", + "p_value": 11198760, + "restrictions": [ + {"cred_def_id": "LjgpST2rjsoxYegQDRm7EL:3:CL:18:tag"} + ], + "non_revoked": {"from": 1579892963, "to": 1579892963}, + }, + }, + "non_revoked": {"from": 1579892963, "to": 1579892963}, + } + big_pres = { + "proof": { + "proofs": [ + { + "primary_proof": "...", + "non_revoc_proof": "...", + } + ], + "aggregated_proof": "...", + }, + "requested_proof": { + "revealed_attrs": { + "17_uuid": { + "sub_proof_index": 0, + "raw": "martini", + "encoded": "943924110058781320304650334433495169960", + } + }, + "revealed_attr_groups": { + "18_uuid": { + "sub_proof_index": 1, + "values": { + "effectiveDate": { + "raw": "2018-01-01", + "encoded": "200737126045061047957925549204159", + }, + "endDate": { + "raw": "", + "encoded": "10993379397001115665086549", + }, + "jurisdictionId": {"raw": "1", "encoded": "1"}, + "legalName": { + "raw": "Flan Nebula", + "encoded": "119967766011746391966411797095112", + }, + "orgTypeId": {"raw": "2", "encoded": "2"}, + }, + } + }, + "self_attested_attrs": {}, + "unrevealed_attrs": {}, + "predicates": { + "18_busid_GE_uuid": {"sub_proof_index": 1}, + "18_id_GE_uuid": {"sub_proof_index": 1}, + }, + }, + "identifiers": [ + { + "schema_id": "LjgpST2rjsoxYegQDRm7EL:2:bc-reg:1.0", + "cred_def_id": "LjgpST2rjsoxYegQDRm7EL:3:CL:17:tag", + "rev_reg_id": None, + "timestamp": None, + }, + { + "schema_id": "LjgpST2rjsoxYegQDRm7EL:2:bc-reg:1.0", + "cred_def_id": "LjgpST2rjsoxYegQDRm7EL:3:CL:18:tag", + "rev_reg_id": None, + "timestamp": None, + }, + ], + } + cred_defs = { + "LjgpST2rjsoxYegQDRm7EL:3:CL:17:tag": {"value": {}}, + "LjgpST2rjsoxYegQDRm7EL:3:CL:18:tag": {"value": {}}, + } + + self.verifier.non_revoc_intervals(big_pres_req, big_pres, cred_defs) + + assert "non_revoked" not in big_pres_req + for spec in big_pres_req["requested_attributes"].values(): + assert "non_revoked" not in spec + for spec in big_pres_req["requested_predicates"].values(): + assert "non_revoked" not in spec + + async def test_pre_verify(self): + with self.assertRaises(ValueError): + await self.verifier.pre_verify( + None, {"requested_proof": "...", "proof": "..."} + ) + with self.assertRaises(ValueError): + await self.verifier.pre_verify( + {"requested_predicates": "...", "requested_attributes": "..."}, + None, + ) + with self.assertRaises(ValueError): + await self.verifier.pre_verify( + {"requested_predicates": "...", "requested_attributes": "..."}, + {"requested_proof": "..."}, + ) + with self.assertRaises(ValueError): + await self.verifier.pre_verify( + {"requested_predicates": "...", "requested_attributes": "..."}, + {"proof": "..."}, + ) + with self.assertRaises(ValueError): + await self.verifier.pre_verify( + { + "requested_predicates": {"0_name_uuid": "..."}, + "requested_attributes": "...", + }, + INDY_PROOF_PRED_NAMES, + ) + with self.assertRaises(ValueError): + await self.verifier.pre_verify( + INDY_PROOF_REQ_NAME, + { + "proof": "...", + "requested_proof": { + "revealed_attrs": {}, + "self_attested_attrs": {"19_uuid": "Chicken Hawk"}, + "unrevealed_attrs": {}, + "predicates": {}, + }, + "identifiers": [ + { + "schema_id": "LjgpST2rjsoxYegQDRm7EL:2:non-revo:1579888926.0", + "cred_def_id": "LjgpST2rjsoxYegQDRm7EL:3:CL:19:tag", + "rev_reg_id": "LjgpST2rjsoxYegQDRm7EL:4:LjgpST2rjsoxYegQDRm7EL:3:CL:18:tag:CL_ACCUM:0", + "timestamp": 1579892963, + } + ], + }, + ) + with self.assertRaises(ValueError): + await self.verifier.pre_verify( + { + "nonce": "15606741555044336341559", + "name": "proof_req", + "version": "0.0", + "requested_attributes": {"19_uuid": {"name": "Preferred Name"}}, + "requested_predicates": {}, + }, + { + "proof": "...", + "requested_proof": { + "revealed_attrs": {}, + "self_attested_attrs": {}, + "unrevealed_attrs": {}, + "predicates": {}, + }, + "identifiers": [ + { + "schema_id": "LjgpST2rjsoxYegQDRm7EL:2:non-revo:1579888926.0", + "cred_def_id": "LjgpST2rjsoxYegQDRm7EL:3:CL:19:tag", + "rev_reg_id": "LjgpST2rjsoxYegQDRm7EL:4:LjgpST2rjsoxYegQDRm7EL:3:CL:18:tag:CL_ACCUM:0", + "timestamp": 1579892963, + } + ], + }, + ) + with self.assertRaises(ValueError): + await self.verifier.pre_verify( + { + "nonce": "15606741555044336341559", + "name": "proof_req", + "version": "0.0", + "requested_attributes": { + "19_uuid": {"neither-name-nor-names": "Preferred Name"} + }, + "requested_predicates": {}, + }, + { + "proof": "...", + "requested_proof": { + "revealed_attrs": { + "19_uuid": { + "sub_proof_index": 0, + "raw": "Chicken Hawk", + "encoded": "94607763023542937648705576709896212619553924110058781320304650334433495169960", + } + }, + "self_attested_attrs": {}, + "unrevealed_attrs": {}, + "predicates": {}, + }, + "identifiers": [ + { + "schema_id": "LjgpST2rjsoxYegQDRm7EL:2:non-revo:1579888926.0", + "cred_def_id": "LjgpST2rjsoxYegQDRm7EL:3:CL:19:tag", + "rev_reg_id": "LjgpST2rjsoxYegQDRm7EL:4:LjgpST2rjsoxYegQDRm7EL:3:CL:18:tag:CL_ACCUM:0", + "timestamp": 1579892963, + } + ], + }, + ) + with self.assertRaises(ValueError): + await self.verifier.pre_verify( + INDY_PROOF_REQ_NAME, + { + "proof": { + "proofs": [ + { + "primary_proof": { + "eq_proof": { + "revealed_attrs": {"otherthing": "..."}, + "...": "...", + }, + "ge_proofs": [], + }, + "...": "...", + } + ], + "...": "...", + }, + "requested_proof": { + "revealed_attrs": { + "19_uuid": { + "sub_proof_index": 0, + "raw": "Chicken Hawk", + "encoded": "94607763023542937648705576709896212619553924110058781320304650334433495169960", + } + }, + "self_attested_attrs": {}, + "unrevealed_attrs": {}, + "predicates": {}, + }, + "identifiers": [ + { + "schema_id": "LjgpST2rjsoxYegQDRm7EL:2:non-revo:1579888926.0", + "cred_def_id": "LjgpST2rjsoxYegQDRm7EL:3:CL:19:tag", + "rev_reg_id": "LjgpST2rjsoxYegQDRm7EL:4:LjgpST2rjsoxYegQDRm7EL:3:CL:18:tag:CL_ACCUM:0", + "timestamp": 1579892963, + } + ], + }, + ) + await self.verifier.pre_verify( + { + "nonce": "15606741555044336341559", + "name": "proof_req", + "version": "0.0", + "requested_attributes": {"19_uuid": {"name": "Preferred Name"}}, + "requested_predicates": {}, + }, + { + "proof": "...", + "requested_proof": { + "revealed_attrs": {}, + "self_attested_attrs": {"19_uuid": "Chicken Hawk"}, + "unrevealed_attrs": {}, + "predicates": {}, + }, + "identifiers": [ + { + "schema_id": "LjgpST2rjsoxYegQDRm7EL:2:non-revo:1579888926.0", + "cred_def_id": "LjgpST2rjsoxYegQDRm7EL:3:CL:19:tag", + "rev_reg_id": "LjgpST2rjsoxYegQDRm7EL:4:LjgpST2rjsoxYegQDRm7EL:3:CL:18:tag:CL_ACCUM:0", + "timestamp": 1579892963, + } + ], + }, + ) + await self.verifier.pre_verify( + { + "nonce": "15606741555044336341559", + "name": "proof_req", + "version": "0.0", + "requested_attributes": {}, + "requested_predicates": { + "18_id_GE_uuid": { + "name": "id", + "p_type": ">=", + "p_value": 4, + "restrictions": [ + {"cred_def_id": "LjgpST2rjsoxYegQDRm7EL:3:CL:19:tag"} + ], + }, + "18_id_LE_uuid": { + "name": "id", + "p_type": "<=", + "p_value": 11198760, + "restrictions": [ + {"cred_def_id": "LjgpST2rjsoxYegQDRm7EL:3:CL:19:tag"} + ], + }, + }, + }, + { + "proof": { + "proofs": [ + { + "primary_proof": { + "eq_proof": {}, + "ge_proofs": [ + { + "predicate": { + "attr_name": "id", + "value": 11198760, + } + }, + { + "predicate": { + "attr_name": "id", + "value": 4, + } + }, + ], + } + } + ], + }, + "requested_proof": { + "revealed_attrs": {}, + "self_attested_attrs": {}, + "unrevealed_attrs": {}, + "predicates": { + "18_id_LE_uuid": {"sub_proof_index": 0}, + "18_id_GE_uuid": {"sub_proof_index": 0}, + }, + }, + "identifiers": [ + { + "schema_id": "LjgpST2rjsoxYegQDRm7EL:2:non-revo:1579888926.0", + "cred_def_id": "LjgpST2rjsoxYegQDRm7EL:3:CL:19:tag", + "rev_reg_id": "LjgpST2rjsoxYegQDRm7EL:4:LjgpST2rjsoxYegQDRm7EL:3:CL:18:tag:CL_ACCUM:0", + "timestamp": 1579892963, + } + ], + }, + ) diff --git a/aries_cloudagent/indy/util.py b/aries_cloudagent/indy/util.py new file mode 100644 index 0000000000..2c9a126c46 --- /dev/null +++ b/aries_cloudagent/indy/util.py @@ -0,0 +1,39 @@ +"""Utilities for dealing with Indy conventions.""" + +from os import getenv, makedirs, urandom +from os.path import isdir, join +from pathlib import Path +from platform import system + + +async def generate_pr_nonce() -> str: + """Generate a nonce for a proof request.""" + # equivalent to indy.anoncreds.generate_nonce + return str(int.from_bytes(urandom(10), "big")) + + +def indy_client_dir(subpath: str = None, create: bool = False) -> str: + """ + Return '/'-terminated subdirectory of indy-client directory. + + Args: + subpath: subpath within indy-client structure + create: whether to create subdirectory if absent + """ + + home = Path.home() + target_dir = join( + home, + "Documents" + if isdir(join(home, "Documents")) + else getenv("EXTERNAL_STORAGE", "") + if system() == "Linux" + else "", + ".indy_client", + subpath if subpath else "", + "", # set trailing separator + ) + if create: + makedirs(target_dir, exist_ok=True) + + return target_dir diff --git a/aries_cloudagent/indy/verifier.py b/aries_cloudagent/indy/verifier.py new file mode 100644 index 0000000000..9a83ce5b8f --- /dev/null +++ b/aries_cloudagent/indy/verifier.py @@ -0,0 +1,411 @@ +"""Base Indy Verifier class.""" + +import logging + +from abc import ABC, ABCMeta, abstractmethod +from enum import Enum +from time import time +from typing import Mapping + +from ..core.profile import Profile +from ..ledger.multiple_ledger.ledger_requests_executor import ( + GET_CRED_DEF, + IndyLedgerRequestsExecutor, +) +from ..messaging.util import canon, encode +from ..multitenant.base import BaseMultitenantManager + +from .models.xform import indy_proof_req2non_revoc_intervals + + +LOGGER = logging.getLogger(__name__) + + +class PresVerifyMsg(str, Enum): + """Credential verification codes.""" + + RMV_REFERENT_NON_REVOC_INTERVAL = "RMV_RFNT_NRI" + RMV_GLOBAL_NON_REVOC_INTERVAL = "RMV_GLB_NRI" + TSTMP_OUT_NON_REVOC_INTRVAL = "TS_OUT_NRI" + CT_UNREVEALED_ATTRIBUTES = "UNRVL_ATTR" + PRES_VALUE_ERROR = "VALUE_ERROR" + PRES_VERIFY_ERROR = "VERIFY_ERROR" + + +class IndyVerifier(ABC, metaclass=ABCMeta): + """Base class for Indy Verifier.""" + + def __repr__(self) -> str: + """ + Return a human readable representation of this class. + + Returns: + A human readable string for this class + + """ + return "<{}>".format(self.__class__.__name__) + + def non_revoc_intervals(self, pres_req: dict, pres: dict, cred_defs: dict) -> list: + """ + 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 + with non-revocable credentials in proof: seek and remove. + + Args: + pres_req: presentation request + pres: corresponding presentation + + """ + msgs = [] + for req_proof_key, pres_key in { + "revealed_attrs": "requested_attributes", + "revealed_attr_groups": "requested_attributes", + "predicates": "requested_predicates", + }.items(): + for uuid, spec in pres["requested_proof"].get(req_proof_key, {}).items(): + if ( + "revocation" + not in cred_defs[ + pres["identifiers"][spec["sub_proof_index"]]["cred_def_id"] + ]["value"] + ): + if uuid in pres_req[pres_key] and pres_req[pres_key][uuid].pop( + "non_revoked", None + ): + msgs.append( + f"{PresVerifyMsg.RMV_REFERENT_NON_REVOC_INTERVAL.value}::" + f"{uuid}" + ) + LOGGER.info( + ( + "Amended presentation request (nonce=%s): removed " + "non-revocation interval at %s referent " + "%s; corresponding credential in proof is irrevocable" + ), + pres_req["nonce"], + pres_key, + uuid, + ) + + if all( + ( + spec.get("timestamp") is None + and "revocation" not in cred_defs[spec["cred_def_id"]]["value"] + ) + for spec in pres["identifiers"] + ): + pres_req.pop("non_revoked", None) + msgs.append(PresVerifyMsg.RMV_GLOBAL_NON_REVOC_INTERVAL.value) + LOGGER.warning( + ( + "Amended presentation request (nonce=%s); removed global " + "non-revocation interval; no revocable credentials in proof" + ), + pres_req["nonce"], + ) + return msgs + + async def check_timestamps( + self, + profile: Profile, + pres_req: Mapping, + pres: Mapping, + rev_reg_defs: Mapping, + ) -> list: + """ + Check for suspicious, missing, and superfluous timestamps. + + Raises ValueError on timestamp in the future, prior to rev reg creation, + superfluous or missing. + + Args: + ledger: the base ledger for retrieving revocation registry definitions + pres_req: indy proof request + pres: indy 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) + LOGGER.debug(f">>> got non-revoc intervals: {non_revoc_intervals}") + # timestamp for irrevocable credential + cred_defs = [] + for index, ident in enumerate(pres["identifiers"]): + LOGGER.debug(f">>> got (index, ident): ({index},{ident})") + cred_def_id = ident["cred_def_id"] + multitenant_mgr = profile.inject_or(BaseMultitenantManager) + if multitenant_mgr: + ledger_exec_inst = IndyLedgerRequestsExecutor(profile) + else: + ledger_exec_inst = 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) + cred_defs.append(cred_def) + if ident.get("timestamp"): + if not cred_def["value"].get("revocation"): + raise ValueError( + f"Timestamp in presentation identifier #{index} " + f"for irrevocable cred def id {cred_def_id}" + ) + + # timestamp in the future too far in the past + for ident in pres["identifiers"]: + timestamp = ident.get("timestamp") + rev_reg_id = ident.get("rev_reg_id") + + if not timestamp: + continue + + if timestamp > now + 300: # allow 5 min for clock skew + raise ValueError(f"Timestamp {timestamp} is in the future") + reg_def = rev_reg_defs.get(rev_reg_id) + if not reg_def: + raise ValueError(f"Missing registry definition for '{rev_reg_id}'") + if "txnTime" not in reg_def: + raise ValueError( + f"Missing txnTime for registry definition '{rev_reg_id}'" + ) + if timestamp < reg_def["txnTime"]: + raise ValueError( + f"Timestamp {timestamp} predates rev reg {rev_reg_id} creation" + ) + + # timestamp superfluous, missing, or outside non-revocation interval + revealed_attrs = pres["requested_proof"].get("revealed_attrs", {}) + unrevealed_attrs = pres["requested_proof"].get("unrevealed_attrs", {}) + revealed_groups = pres["requested_proof"].get("revealed_attr_groups", {}) + self_attested = pres["requested_proof"].get("self_attested_attrs", {}) + preds = pres["requested_proof"].get("predicates", {}) + for uuid, req_attr in pres_req["requested_attributes"].items(): + if "name" in req_attr: + if uuid in revealed_attrs: + index = revealed_attrs[uuid]["sub_proof_index"] + if cred_defs[index]["value"].get("revocation"): + timestamp = pres["identifiers"][index].get("timestamp") + if (timestamp is not None) ^ bool( + non_revoc_intervals.get(uuid) + ): + LOGGER.debug(f">>> uuid: {uuid}") + LOGGER.debug( + f">>> revealed_attrs[uuid]: {revealed_attrs[uuid]}" + ) + raise ValueError( + f"Timestamp on sub-proof #{index} " + f"is {'superfluous' if timestamp else 'missing'} " + f"vs. requested attribute {uuid}" + ) + if non_revoc_intervals.get(uuid) and not ( + non_revoc_intervals[uuid].get("from", 0) + < timestamp + < non_revoc_intervals[uuid].get("to", now) + ): + msgs.append( + f"{PresVerifyMsg.TSTMP_OUT_NON_REVOC_INTRVAL.value}::" + f"{uuid}" + ) + LOGGER.info( + f"Timestamp {timestamp} from ledger for item" + f"{uuid} falls outside non-revocation interval " + f"{non_revoc_intervals[uuid]}" + ) + elif uuid in unrevealed_attrs: + # nothing to do, attribute value is not revealed + msgs.append( + f"{PresVerifyMsg.CT_UNREVEALED_ATTRIBUTES.value}::" f"{uuid}" + ) + elif uuid not in self_attested: + raise ValueError( + f"Presentation attributes mismatch requested attribute {uuid}" + ) + + elif "names" in req_attr: + group_spec = revealed_groups.get(uuid) + if ( + group_spec is None + or "sub_proof_index" not in group_spec + or "values" not in group_spec + ): + raise ValueError(f"Missing requested attribute group {uuid}") + index = group_spec["sub_proof_index"] + if cred_defs[index]["value"].get("revocation"): + timestamp = pres["identifiers"][index].get("timestamp") + if (timestamp is not None) ^ bool(non_revoc_intervals.get(uuid)): + raise ValueError( + f"Timestamp on sub-proof #{index} " + f"is {'superfluous' if timestamp else 'missing'} " + f"vs. requested attribute group {uuid}" + ) + if non_revoc_intervals.get(uuid) and not ( + non_revoc_intervals[uuid].get("from", 0) + < timestamp + < non_revoc_intervals[uuid].get("to", now) + ): + msgs.append( + f"{PresVerifyMsg.TSTMP_OUT_NON_REVOC_INTRVAL.value}::" + f"{uuid}" + ) + LOGGER.warning( + f"Timestamp {timestamp} from ledger for item" + f"{uuid} falls outside non-revocation interval " + f"{non_revoc_intervals[uuid]}" + ) + + for uuid, req_pred in pres_req["requested_predicates"].items(): + pred_spec = preds.get(uuid) + if pred_spec is None or "sub_proof_index" not in pred_spec: + raise ValueError( + f"Presentation predicates mismatch requested predicate {uuid}" + ) + index = pred_spec["sub_proof_index"] + if cred_defs[index]["value"].get("revocation"): + timestamp = pres["identifiers"][index].get("timestamp") + if (timestamp is not None) ^ bool(non_revoc_intervals.get(uuid)): + raise ValueError( + f"Timestamp on sub-proof #{index} " + f"is {'superfluous' if timestamp else 'missing'} " + f"vs. requested predicate {uuid}" + ) + if non_revoc_intervals.get(uuid) and not ( + non_revoc_intervals[uuid].get("from", 0) + < timestamp + < non_revoc_intervals[uuid].get("to", now) + ): + msgs.append( + f"{PresVerifyMsg.TSTMP_OUT_NON_REVOC_INTRVAL.value}::" f"{uuid}" + ) + LOGGER.warning( + f"Best-effort timestamp {timestamp} " + "from ledger falls outside non-revocation interval " + f"{non_revoc_intervals[uuid]}" + ) + return msgs + + async def pre_verify(self, pres_req: dict, pres: dict) -> list: + """ + Check for essential components and tampering in presentation. + + Visit encoded attribute values against raw, and predicate bounds, + in presentation, cross-reference against presentation request. + + Args: + pres_req: presentation request + pres: corresponding presentation + + """ + msgs = [] + if not ( + pres_req + and "requested_predicates" in pres_req + and "requested_attributes" in pres_req + ): + raise ValueError("Incomplete or missing proof request") + if not pres: + raise ValueError("No proof provided") + if "requested_proof" not in pres: + raise ValueError("Presentation missing 'requested_proof'") + if "proof" not in pres: + raise ValueError("Presentation missing 'proof'") + + for uuid, req_pred in pres_req["requested_predicates"].items(): + try: + canon_attr = canon(req_pred["name"]) + matched = False + found = False + for ge_proof in pres["proof"]["proofs"][ + pres["requested_proof"]["predicates"][uuid]["sub_proof_index"] + ]["primary_proof"]["ge_proofs"]: + pred = ge_proof["predicate"] + if pred["attr_name"] == canon_attr: + found = True + if pred["value"] == req_pred["p_value"]: + matched = True + break + if not matched: + raise ValueError(f"Predicate value != p_value: {pred['attr_name']}") + break + elif not found: + raise ValueError(f"Missing requested predicate '{uuid}'") + except (KeyError, TypeError): + raise ValueError(f"Missing requested predicate '{uuid}'") + + revealed_attrs = pres["requested_proof"].get("revealed_attrs", {}) + unrevealed_attrs = pres["requested_proof"].get("unrevealed_attrs", {}) + revealed_groups = pres["requested_proof"].get("revealed_attr_groups", {}) + self_attested = pres["requested_proof"].get("self_attested_attrs", {}) + for uuid, req_attr in pres_req["requested_attributes"].items(): + if "name" in req_attr: + if uuid in revealed_attrs: + pres_req_attr_spec = {req_attr["name"]: revealed_attrs[uuid]} + elif uuid in unrevealed_attrs: + # unrevealed attribute, nothing to do + pres_req_attr_spec = {} + msgs.append( + f"{PresVerifyMsg.CT_UNREVEALED_ATTRIBUTES.value}::" f"{uuid}" + ) + elif uuid in self_attested: + if not req_attr.get("restrictions"): + continue + raise ValueError( + "Attribute with restrictions cannot be self-attested: " + f"'{req_attr['name']}'" + ) + else: + raise ValueError( + f"Missing requested attribute '{req_attr['name']}'" + ) + elif "names" in req_attr: + group_spec = revealed_groups[uuid] + pres_req_attr_spec = { + attr: { + "sub_proof_index": group_spec["sub_proof_index"], + **group_spec["values"].get(attr), + } + for attr in req_attr["names"] + } + else: + raise ValueError( + f"Request attribute missing 'name' and 'names': '{uuid}'" + ) + + for attr, spec in pres_req_attr_spec.items(): + try: + primary_enco = pres["proof"]["proofs"][spec["sub_proof_index"]][ + "primary_proof" + ]["eq_proof"]["revealed_attrs"][canon(attr)] + except (KeyError, TypeError): + raise ValueError(f"Missing revealed attribute: '{attr}'") + if primary_enco != spec["encoded"]: + raise ValueError(f"Encoded representation mismatch for '{attr}'") + if primary_enco != encode(spec["raw"]): + raise ValueError(f"Encoded representation mismatch for '{attr}'") + return msgs + + @abstractmethod + def verify_presentation( + self, + presentation_request, + presentation, + schemas, + credential_definitions, + rev_reg_defs, + rev_reg_entries, + ) -> (bool, list): + """ + Verify a presentation. + + Args: + presentation_request: Presentation request data + presentation: Presentation data + schemas: Schema data + credential_definitions: credential definition data + rev_reg_defs: revocation registry definitions + rev_reg_entries: revocation registry entries + """ From 170f2154a457dcb1b03241f8a2e4fd4a98dd6726 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Thu, 30 Mar 2023 09:37:18 -0600 Subject: [PATCH 089/150] feat: remove sdk and credx packages Signed-off-by: Char Howland --- aries_cloudagent/anoncreds/credx/__init__.py | 0 aries_cloudagent/anoncreds/credx/holder.py | 588 ---------------- aries_cloudagent/anoncreds/credx/issuer.py | 639 ------------------ .../anoncreds/credx/tests/__init__.py | 0 .../credx/tests/test_cred_issuance.py | 341 ---------- aries_cloudagent/anoncreds/credx/verifier.py | 85 --- aries_cloudagent/anoncreds/sdk/__init__.py | 0 aries_cloudagent/anoncreds/sdk/error.py | 43 -- aries_cloudagent/anoncreds/sdk/holder.py | 483 ------------- aries_cloudagent/anoncreds/sdk/issuer.py | 385 ----------- aries_cloudagent/anoncreds/sdk/profile.py | 198 ------ .../anoncreds/sdk/tests/__init__.py | 0 .../anoncreds/sdk/tests/test_holder.py | 604 ----------------- .../anoncreds/sdk/tests/test_issuer.py | 391 ----------- .../anoncreds/sdk/tests/test_profile.py | 86 --- .../anoncreds/sdk/tests/test_util.py | 46 -- .../anoncreds/sdk/tests/test_verifier.py | 596 ---------------- .../anoncreds/sdk/tests/test_wallet_plugin.py | 129 ---- aries_cloudagent/anoncreds/sdk/util.py | 29 - aries_cloudagent/anoncreds/sdk/verifier.py | 88 --- .../anoncreds/sdk/wallet_plugin.py | 63 -- .../anoncreds/sdk/wallet_setup.py | 236 ------- 22 files changed, 5030 deletions(-) delete mode 100644 aries_cloudagent/anoncreds/credx/__init__.py delete mode 100644 aries_cloudagent/anoncreds/credx/holder.py delete mode 100644 aries_cloudagent/anoncreds/credx/issuer.py delete mode 100644 aries_cloudagent/anoncreds/credx/tests/__init__.py delete mode 100644 aries_cloudagent/anoncreds/credx/tests/test_cred_issuance.py delete mode 100644 aries_cloudagent/anoncreds/credx/verifier.py delete mode 100644 aries_cloudagent/anoncreds/sdk/__init__.py delete mode 100644 aries_cloudagent/anoncreds/sdk/error.py delete mode 100644 aries_cloudagent/anoncreds/sdk/holder.py delete mode 100644 aries_cloudagent/anoncreds/sdk/issuer.py delete mode 100644 aries_cloudagent/anoncreds/sdk/profile.py delete mode 100644 aries_cloudagent/anoncreds/sdk/tests/__init__.py delete mode 100644 aries_cloudagent/anoncreds/sdk/tests/test_holder.py delete mode 100644 aries_cloudagent/anoncreds/sdk/tests/test_issuer.py delete mode 100644 aries_cloudagent/anoncreds/sdk/tests/test_profile.py delete mode 100644 aries_cloudagent/anoncreds/sdk/tests/test_util.py delete mode 100644 aries_cloudagent/anoncreds/sdk/tests/test_verifier.py delete mode 100644 aries_cloudagent/anoncreds/sdk/tests/test_wallet_plugin.py delete mode 100644 aries_cloudagent/anoncreds/sdk/util.py delete mode 100644 aries_cloudagent/anoncreds/sdk/verifier.py delete mode 100644 aries_cloudagent/anoncreds/sdk/wallet_plugin.py delete mode 100644 aries_cloudagent/anoncreds/sdk/wallet_setup.py diff --git a/aries_cloudagent/anoncreds/credx/__init__.py b/aries_cloudagent/anoncreds/credx/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/aries_cloudagent/anoncreds/credx/holder.py b/aries_cloudagent/anoncreds/credx/holder.py deleted file mode 100644 index ba53d23730..0000000000 --- a/aries_cloudagent/anoncreds/credx/holder.py +++ /dev/null @@ -1,588 +0,0 @@ -"""Indy holder implementation.""" - -import asyncio -import json -import logging -import re -import uuid - -from typing import Dict, Sequence, Tuple, Union - -from aries_askar import AskarError, AskarErrorCode -from indy_credx import ( - CredxError, - Credential, - CredentialRequest, - CredentialRevocationState, - MasterSecret, - Presentation, - PresentCredentials, -) - -from ...askar.profile import AskarProfile -from ...ledger.base import BaseLedger -from ...wallet.error import WalletNotFoundError - -from ..holder import AnonCredsHolder, AnonCredsHolderError - -LOGGER = logging.getLogger(__name__) - -CATEGORY_CREDENTIAL = "credential" -CATEGORY_MASTER_SECRET = "master_secret" - - -def _make_cred_info(cred_id, cred: Credential): - cred_info = cred.to_dict() # not secure! - rev_info = cred_info["signature"]["r_credential"] - return { - "referent": cred_id, - "schema_id": cred_info["schema_id"], - "cred_def_id": cred_info["cred_def_id"], - "rev_reg_id": cred_info["rev_reg_id"], - "cred_rev_id": str(rev_info["i"]) if rev_info else None, - "attrs": {name: val["raw"] for (name, val) in cred_info["values"].items()}, - } - - -def _normalize_attr_name(name: str) -> str: - return name.replace(" ", "") - - -class IndyCredxHolder(AnonCredsHolder): - """Indy-credx holder class.""" - - MASTER_SECRET_ID = "default" - - def __init__(self, profile: AskarProfile): - """ - Initialize an IndyCredxHolder instance. - - Args: - profile: The active profile instance - - """ - self._profile = profile - - @property - def profile(self) -> AskarProfile: - """Accessor for the profile instance.""" - return self._profile - - async def get_master_secret(self) -> MasterSecret: - """Get or create the default master secret.""" - - while True: - async with self._profile.session() as session: - try: - record = await session.handle.fetch( - CATEGORY_MASTER_SECRET, IndyCredxHolder.MASTER_SECRET_ID - ) - except AskarError as err: - raise AnonCredsHolderError("Error fetching master secret") from err - if record: - try: - secret = MasterSecret.load(record.raw_value) - except CredxError as err: - raise AnonCredsHolderError( - "Error loading master secret" - ) from err - break - else: - try: - secret = MasterSecret.create() - except CredxError as err: - raise AnonCredsHolderError( - "Error creating master secret" - ) from err - try: - await session.handle.insert( - CATEGORY_MASTER_SECRET, - IndyCredxHolder.MASTER_SECRET_ID, - secret.to_json_buffer(), - ) - except AskarError as err: - if err.code != AskarErrorCode.DUPLICATE: - raise AnonCredsHolderError( - "Error saving master secret" - ) from err - # else: lost race to create record, retry - else: - break - return secret - - async def create_credential_request( - self, credential_offer: dict, credential_definition: dict, holder_did: str - ) -> Tuple[str, str]: - """ - Create a credential request for the given credential offer. - - Args: - credential_offer: The credential offer to create request for - credential_definition: The credential definition to create an offer for - holder_did: the DID of the agent making the request - - Returns: - A tuple of the credential request and credential request metadata - - """ - try: - secret = await self.get_master_secret() - ( - cred_req, - cred_req_metadata, - ) = await asyncio.get_event_loop().run_in_executor( - None, - CredentialRequest.create, - holder_did, - credential_definition, - secret, - IndyCredxHolder.MASTER_SECRET_ID, - credential_offer, - ) - except CredxError as err: - raise AnonCredsHolderError("Error creating credential request") from err - cred_req_json, cred_req_metadata_json = ( - cred_req.to_json(), - cred_req_metadata.to_json(), - ) - - LOGGER.debug( - "Created credential request. " - "credential_request_json=%s credential_request_metadata_json=%s", - cred_req_json, - cred_req_metadata_json, - ) - - return cred_req_json, cred_req_metadata_json - - async def store_credential( - self, - credential_definition: dict, - credential_data: dict, - credential_request_metadata: dict, - credential_attr_mime_types: dict = None, - credential_id: str = None, - rev_reg_def: dict = None, - ) -> str: - """ - Store a credential in the wallet. - - Args: - credential_definition: Credential definition for this credential - credential_data: Credential data generated by the issuer - credential_request_metadata: credential request metadata generated - by the issuer - credential_attr_mime_types: dict mapping attribute names to (optional) - MIME types to store as non-secret record, if specified - credential_id: optionally override the stored credential id - rev_reg_def: revocation registry definition in json - - Returns: - the ID of the stored credential - - """ - try: - secret = await self.get_master_secret() - cred = Credential.load(credential_data) - cred_recvd = await asyncio.get_event_loop().run_in_executor( - None, - cred.process, - credential_request_metadata, - secret, - credential_definition, - rev_reg_def, - ) - except CredxError as err: - raise AnonCredsHolderError("Error processing received credential") from err - - schema_id = cred_recvd.schema_id - schema_id_parts = re.match(r"^(\w+):2:([^:]+):([^:]+)$", schema_id) - if not schema_id_parts: - raise AnonCredsHolderError( - f"Error parsing credential schema ID: {schema_id}" - ) - cred_def_id = cred_recvd.cred_def_id - cdef_id_parts = re.match(r"^(\w+):3:CL:([^:]+):([^:]+)$", cred_def_id) - if not cdef_id_parts: - raise AnonCredsHolderError( - f"Error parsing credential definition ID: {cred_def_id}" - ) - - credential_id = credential_id or str(uuid.uuid4()) - tags = { - "schema_id": schema_id, - "schema_issuer_did": schema_id_parts[1], - "schema_name": schema_id_parts[2], - "schema_version": schema_id_parts[3], - "issuer_did": cdef_id_parts[1], - "cred_def_id": cred_def_id, - "rev_reg_id": cred_recvd.rev_reg_id or "None", - } - - # FIXME - sdk has some special handling for fully qualified DIDs here - - mime_types = {} - for k, attr_value in credential_data["values"].items(): - attr_name = _normalize_attr_name(k) - # tags[f"attr::{attr_name}::marker"] = "1" - tags[f"attr::{attr_name}::value"] = attr_value["raw"] - if credential_attr_mime_types and k in credential_attr_mime_types: - mime_types[k] = credential_attr_mime_types[k] - - try: - async with self._profile.transaction() as txn: - await txn.handle.insert( - CATEGORY_CREDENTIAL, - credential_id, - cred_recvd.to_json_buffer(), - tags=tags, - ) - if mime_types: - await txn.handle.insert( - AnonCredsHolder.RECORD_TYPE_MIME_TYPES, - credential_id, - value_json=mime_types, - ) - await txn.commit() - except AskarError as err: - raise AnonCredsHolderError("Error storing credential") from err - - return credential_id - - async def get_credentials(self, start: int, count: int, wql: dict): - """ - Get credentials stored in the wallet. - - Args: - start: Starting index - count: Number of records to return - wql: wql query dict - - """ - - result = [] - - try: - rows = self._profile.store.scan( - CATEGORY_CREDENTIAL, - wql, - start, - count, - self._profile.settings.get("wallet.askar_profile"), - ) - async for row in rows: - cred = Credential.load(row.raw_value) - result.append(_make_cred_info(row.name, cred)) - except AskarError as err: - raise AnonCredsHolderError("Error retrieving credentials") from err - except CredxError as err: - raise AnonCredsHolderError("Error loading stored credential") from err - - return result - - async def get_credentials_for_presentation_request_by_referent( - self, - presentation_request: dict, - referents: Sequence[str], - start: int, - count: int, - extra_query: dict = {}, - ): - """ - Get credentials stored in the wallet. - - Args: - presentation_request: Valid presentation request from issuer - referents: Presentation request referents to use to search for creds - start: Starting index - count: Maximum number of records to return - extra_query: wql query dict - - """ - - if not referents: - referents = ( - *presentation_request["requested_attributes"], - *presentation_request["requested_predicates"], - ) - - creds = {} - - for reft in referents: - names = set() - if reft in presentation_request["requested_attributes"]: - attr = presentation_request["requested_attributes"][reft] - if "name" in attr: - names.add(_normalize_attr_name(attr["name"])) - elif "names" in attr: - names.update(_normalize_attr_name(name) for name in attr["names"]) - # for name in names: - # tag_filter[f"attr::{_normalize_attr_name(name)}::marker"] = "1" - restr = attr.get("restrictions") - elif reft in presentation_request["requested_predicates"]: - pred = presentation_request["requested_predicates"][reft] - if "name" in pred: - names.add(_normalize_attr_name(pred["name"])) - # tag_filter[f"attr::{_normalize_attr_name(name)}::marker"] = "1" - restr = pred.get("restrictions") - else: - raise AnonCredsHolderError( - f"Unknown presentation request referent: {reft}" - ) - - tag_filter = {"$exist": list(f"attr::{name}::value" for name in names)} - if restr: - # FIXME check if restr is a list or dict? validate WQL format - tag_filter = {"$and": [tag_filter] + restr} - if extra_query: - tag_filter = {"$and": [tag_filter, extra_query]} - - rows = self._profile.store.scan( - CATEGORY_CREDENTIAL, - tag_filter, - start, - count, - self._profile.settings.get("wallet.askar_profile"), - ) - async for row in rows: - if row.name in creds: - creds[row.name]["presentation_referents"].add(reft) - else: - cred_info = _make_cred_info( - row.name, Credential.load(row.raw_value) - ) - creds[row.name] = { - "cred_info": cred_info, - "interval": presentation_request.get("non_revoked"), - "presentation_referents": {reft}, - } - - for cred in creds.values(): - cred["presentation_referents"] = list(cred["presentation_referents"]) - - return list(creds.values()) - - async def get_credential(self, credential_id: str) -> str: - """ - Get a credential stored in the wallet. - - Args: - credential_id: Credential id to retrieve - - """ - cred = await self._get_credential(credential_id) - return json.dumps(_make_cred_info(credential_id, cred)) - - async def _get_credential(self, credential_id: str) -> Credential: - """Get an unencoded Credential instance from the store.""" - try: - async with self._profile.session() as session: - cred = await session.handle.fetch(CATEGORY_CREDENTIAL, credential_id) - except AskarError as err: - raise AnonCredsHolderError("Error retrieving credential") from err - - if not cred: - raise WalletNotFoundError( - f"Credential {credential_id} not found in wallet {self.profile.name}" - ) - - try: - return Credential.load(cred.raw_value) - except CredxError as err: - raise AnonCredsHolderError("Error loading requested credential") from err - - async def credential_revoked( - self, ledger: BaseLedger, credential_id: str, fro: int = None, to: int = None - ) -> bool: - """ - Check ledger for revocation status of credential by cred id. - - Args: - credential_id: Credential id to check - - """ - cred = await self._get_credential(credential_id) - rev_reg_id = cred.rev_reg_id - - if rev_reg_id: - cred_rev_id = cred.rev_reg_index - (rev_reg_delta, _) = await ledger.get_revoc_reg_delta( - rev_reg_id, - fro, - to, - ) - return cred_rev_id in rev_reg_delta["value"].get("revoked", []) - else: - return False - - async def delete_credential(self, credential_id: str): - """ - Remove a credential stored in the wallet. - - Args: - credential_id: Credential id to remove - - """ - try: - async with self._profile.session() as session: - await session.handle.remove(CATEGORY_CREDENTIAL, credential_id) - await session.handle.remove( - AnonCredsHolder.RECORD_TYPE_MIME_TYPES, credential_id - ) - except AskarError as err: - if err.code == AskarErrorCode.NOT_FOUND: - pass - else: - raise AnonCredsHolderError("Error deleting credential") from err - - async def get_mime_type( - self, credential_id: str, attr: str = None - ) -> Union[dict, str]: - """ - Get MIME type per attribute (or for all attributes). - - Args: - credential_id: credential id - attr: attribute of interest or omit for all - - Returns: Attribute MIME type or dict mapping attribute names to MIME types - attr_meta_json = all_meta.tags.get(attr) - - """ - try: - async with self._profile.session() as session: - mime_types_record = await session.handle.fetch( - AnonCredsHolder.RECORD_TYPE_MIME_TYPES, - credential_id, - ) - except AskarError as err: - raise AnonCredsHolderError( - "Error retrieving credential mime types" - ) from err - values = mime_types_record and mime_types_record.value_json - if values: - return values.get(attr) if attr else values - - async def create_presentation( - self, - presentation_request: dict, - requested_credentials: dict, - schemas: dict, - credential_definitions: dict, - rev_states: dict = None, - ) -> str: - """ - Get credentials stored in the wallet. - - Args: - presentation_request: Valid indy format presentation request - requested_credentials: Indy format requested credentials - schemas: Indy formatted schemas JSON - credential_definitions: Indy formatted credential definitions JSON - rev_states: Indy format revocation states JSON - - """ - - creds: Dict[str, Credential] = {} - - def get_rev_state(cred_id: str, detail: dict): - cred = creds[cred_id] - rev_reg_id = cred.rev_reg_id - timestamp = detail.get("timestamp") if rev_reg_id else None - rev_state = None - if timestamp: - if not rev_states or rev_reg_id not in rev_states: - raise AnonCredsHolderError( - f"No revocation states provided for credential '{cred_id}' " - f"with rev_reg_id '{rev_reg_id}'" - ) - rev_state = rev_states[rev_reg_id].get(timestamp) - if not rev_state: - raise AnonCredsHolderError( - f"No revocation states provided for credential '{cred_id}' " - f"with rev_reg_id '{rev_reg_id}' at timestamp {timestamp}" - ) - return timestamp, rev_state - - self_attest = requested_credentials.get("self_attested_attributes") or {} - present_creds = PresentCredentials() - req_attrs = requested_credentials.get("requested_attributes") or {} - for reft, detail in req_attrs.items(): - cred_id = detail["cred_id"] - if cred_id not in creds: - # NOTE: could be optimized if multiple creds are requested - creds[cred_id] = await self._get_credential(cred_id) - timestamp, rev_state = get_rev_state(cred_id, detail) - present_creds.add_attributes( - creds[cred_id], - reft, - reveal=detail["revealed"], - timestamp=timestamp, - rev_state=rev_state, - ) - req_preds = requested_credentials.get("requested_predicates") or {} - for reft, detail in req_preds.items(): - cred_id = detail["cred_id"] - if cred_id not in creds: - # NOTE: could be optimized if multiple creds are requested - creds[cred_id] = await self._get_credential(cred_id) - timestamp, rev_state = get_rev_state(cred_id, detail) - present_creds.add_predicates( - creds[cred_id], - reft, - timestamp=timestamp, - rev_state=rev_state, - ) - - try: - secret = await self.get_master_secret() - presentation = await asyncio.get_event_loop().run_in_executor( - None, - Presentation.create, - presentation_request, - present_creds, - self_attest, - secret, - schemas.values(), - credential_definitions.values(), - ) - except CredxError as err: - raise AnonCredsHolderError("Error creating presentation") from err - - return presentation.to_json() - - async def create_revocation_state( - self, - cred_rev_id: str, - rev_reg_def: dict, - rev_reg_delta: dict, - timestamp: int, - tails_file_path: str, - ) -> str: - """ - Create current revocation state for a received credential. - - Args: - cred_rev_id: credential revocation id in revocation registry - rev_reg_def: revocation registry definition - rev_reg_delta: revocation delta - timestamp: delta timestamp - - Returns: - the revocation state - - """ - - try: - rev_state = await asyncio.get_event_loop().run_in_executor( - None, - CredentialRevocationState.create, - rev_reg_def, - rev_reg_delta, - int(cred_rev_id), - timestamp, - tails_file_path, - ) - except CredxError as err: - raise AnonCredsHolderError("Error creating revocation state") from err - return rev_state.to_json() diff --git a/aries_cloudagent/anoncreds/credx/issuer.py b/aries_cloudagent/anoncreds/credx/issuer.py deleted file mode 100644 index 019516c6e5..0000000000 --- a/aries_cloudagent/anoncreds/credx/issuer.py +++ /dev/null @@ -1,639 +0,0 @@ -"""Indy issuer implementation.""" - -import asyncio -import logging - -from typing import Sequence, Tuple - -from aries_askar import AskarError - -from indy_credx import ( - Credential, - CredentialDefinition, - CredentialOffer, - CredentialRevocationConfig, - CredxError, - RevocationRegistry, - RevocationRegistryDefinition, - RevocationRegistryDelta, - Schema, -) - -from ...askar.profile import AskarProfile - -from ..issuer import ( - AnonCredsIssuer, - AnonCredsIssuerError, - AnonCredsIssuerRevocationRegistryFullError, - DEFAULT_CRED_DEF_TAG, - DEFAULT_SIGNATURE_TYPE, -) - -LOGGER = logging.getLogger(__name__) - -CATEGORY_CRED_DEF = "credential_def" -CATEGORY_CRED_DEF_PRIVATE = "credential_def_private" -CATEGORY_CRED_DEF_KEY_PROOF = "credential_def_key_proof" -CATEGORY_SCHEMA = "schema" -CATEGORY_REV_REG = "revocation_reg" -CATEGORY_REV_REG_INFO = "revocation_reg_info" -CATEGORY_REV_REG_DEF = "revocation_reg_def" -CATEGORY_REV_REG_DEF_PRIVATE = "revocation_reg_def_private" -CATEGORY_REV_REG_ISSUER = "revocation_reg_def_issuer" - - -class IndyCredxIssuer(AnonCredsIssuer): - """Indy-Credx issuer class.""" - - def __init__(self, profile: AskarProfile): - """ - Initialize an IndyCredxIssuer instance. - - Args: - profile: The active profile instance - - """ - self._profile = profile - - @property - def profile(self) -> AskarProfile: - """Accessor for the profile instance.""" - return self._profile - - async def create_schema( - self, - origin_did: str, - schema_name: str, - schema_version: str, - attribute_names: Sequence[str], - ) -> Tuple[str, str]: - """ - Create a new credential schema and store it in the wallet. - - Args: - origin_did: the DID issuing the credential definition - schema_name: the schema name - schema_version: the schema version - attribute_names: a sequence of schema attribute names - - Returns: - A tuple of the schema ID and JSON - - """ - try: - schema = Schema.create( - origin_did, schema_name, schema_version, attribute_names - ) - schema_id = schema.id - schema_json = schema.to_json() - async with self._profile.session() as session: - await session.handle.insert(CATEGORY_SCHEMA, schema_id, schema_json) - except CredxError as err: - raise AnonCredsIssuerError("Error creating schema") from err - except AskarError as err: - raise AnonCredsIssuerError("Error storing schema") from err - return (schema_id, schema_json) - - async def credential_definition_in_wallet( - self, credential_definition_id: str - ) -> bool: - """ - Check whether a given credential definition ID is present in the wallet. - - Args: - credential_definition_id: The credential definition ID to check - """ - try: - async with self._profile.session() as session: - return ( - await session.handle.fetch( - CATEGORY_CRED_DEF_PRIVATE, credential_definition_id - ) - ) is not None - except AskarError as err: - raise AnonCredsIssuerError( - "Error checking for credential definition" - ) from err - - async def create_and_store_credential_definition( - self, - origin_did: str, - schema: dict, - signature_type: str = None, - tag: str = None, - support_revocation: bool = False, - ) -> Tuple[str, str]: - """ - Create a new credential definition and store it in the wallet. - - Args: - origin_did: the DID issuing the credential definition - schema_json: the schema used as a basis - signature_type: the credential definition signature type (default 'CL') - tag: the credential definition tag - support_revocation: whether to enable revocation for this credential def - - Returns: - A tuple of the credential definition ID and JSON - - """ - try: - ( - cred_def, - cred_def_private, - key_proof, - ) = await asyncio.get_event_loop().run_in_executor( - None, - lambda: CredentialDefinition.create( - origin_did, - schema, - signature_type or DEFAULT_SIGNATURE_TYPE, - tag or DEFAULT_CRED_DEF_TAG, - support_revocation=support_revocation, - ), - ) - cred_def_id = cred_def.id - cred_def_json = cred_def.to_json() - except CredxError as err: - raise AnonCredsIssuerError("Error creating credential definition") from err - try: - async with self._profile.transaction() as txn: - await txn.handle.insert( - CATEGORY_CRED_DEF, - cred_def_id, - cred_def_json, - # Note: Indy-SDK uses a separate SchemaId record for this - tags={"schema_id": schema["id"]}, - ) - await txn.handle.insert( - CATEGORY_CRED_DEF_PRIVATE, - cred_def_id, - cred_def_private.to_json_buffer(), - ) - await txn.handle.insert( - CATEGORY_CRED_DEF_KEY_PROOF, cred_def_id, key_proof.to_json_buffer() - ) - await txn.commit() - except AskarError as err: - raise AnonCredsIssuerError("Error storing credential definition") from err - return (cred_def_id, cred_def_json) - - async def create_credential_offer(self, credential_definition_id: str) -> str: - """ - Create a credential offer for the given credential definition id. - - Args: - credential_definition_id: The credential definition to create an offer for - - Returns: - The new credential offer - - """ - try: - async with self._profile.session() as session: - cred_def = await session.handle.fetch( - CATEGORY_CRED_DEF, credential_definition_id - ) - key_proof = await session.handle.fetch( - CATEGORY_CRED_DEF_KEY_PROOF, credential_definition_id - ) - except AskarError as err: - raise AnonCredsIssuerError( - "Error retrieving credential definition" - ) from err - if not cred_def or not key_proof: - raise AnonCredsIssuerError( - "Credential definition not found for credential offer" - ) - try: - # The tag holds the full name of the schema, - # as opposed to just the sequence number - schema_id = cred_def.tags.get("schema_id") - cred_def = CredentialDefinition.load(cred_def.raw_value) - - credential_offer = CredentialOffer.create( - schema_id or cred_def.schema_id, - cred_def, - key_proof.raw_value, - ) - except CredxError as err: - raise AnonCredsIssuerError("Error creating credential offer") from err - - return credential_offer.to_json() - - async def create_credential( - self, - schema: dict, - credential_offer: dict, - credential_request: dict, - credential_values: dict, - revoc_reg_id: str = None, - tails_file_path: str = None, - ) -> Tuple[str, str]: - """ - Create a credential. - - Args - schema: Schema to create credential for - credential_offer: Credential Offer to create credential for - credential_request: Credential request to create credential for - credential_values: Values to go in credential - revoc_reg_id: ID of the revocation registry - tails_file_path: The location of the tails file - - Returns: - A tuple of created credential and revocation id - - """ - credential_definition_id = credential_offer["cred_def_id"] - try: - async with self._profile.session() as session: - cred_def = await session.handle.fetch( - CATEGORY_CRED_DEF, credential_definition_id - ) - cred_def_private = await session.handle.fetch( - CATEGORY_CRED_DEF_PRIVATE, credential_definition_id - ) - except AskarError as err: - raise AnonCredsIssuerError( - "Error retrieving credential definition" - ) from err - if not cred_def or not cred_def_private: - raise AnonCredsIssuerError( - "Credential definition not found for credential issuance" - ) - - raw_values = {} - schema_attributes = schema["attrNames"] - for attribute in schema_attributes: - # Ensure every attribute present in schema to be set. - # Extraneous attribute names are ignored. - try: - credential_value = credential_values[attribute] - except KeyError: - raise AnonCredsIssuerError( - "Provided credential values are missing a value " - f"for the schema attribute '{attribute}'" - ) - - raw_values[attribute] = str(credential_value) - - if revoc_reg_id: - try: - async with self._profile.transaction() as txn: - rev_reg = await txn.handle.fetch(CATEGORY_REV_REG, revoc_reg_id) - rev_reg_info = await txn.handle.fetch( - CATEGORY_REV_REG_INFO, revoc_reg_id, for_update=True - ) - rev_reg_def = await txn.handle.fetch( - CATEGORY_REV_REG_DEF, revoc_reg_id - ) - rev_key = await txn.handle.fetch( - CATEGORY_REV_REG_DEF_PRIVATE, revoc_reg_id - ) - if not rev_reg: - raise AnonCredsIssuerError("Revocation registry not found") - if not rev_reg_info: - raise AnonCredsIssuerError( - "Revocation registry metadata not found" - ) - if not rev_reg_def: - raise AnonCredsIssuerError( - "Revocation registry definition not found" - ) - if not rev_key: - raise AnonCredsIssuerError( - "Revocation registry definition private data not found" - ) - # NOTE: we increment the index ahead of time to keep the - # transaction short. The revocation registry itself will NOT - # be updated because we always use ISSUANCE_BY_DEFAULT. - # If something goes wrong later, the index will be skipped. - # FIXME - double check issuance type in case of upgraded wallet? - rev_info = rev_reg_info.value_json - rev_reg_index = rev_info["curr_id"] + 1 - try: - rev_reg_def = RevocationRegistryDefinition.load( - rev_reg_def.raw_value - ) - except CredxError as err: - raise AnonCredsIssuerError( - "Error loading revocation registry definition" - ) from err - if rev_reg_index > rev_reg_def.max_cred_num: - raise AnonCredsIssuerRevocationRegistryFullError( - "Revocation registry is full" - ) - rev_info["curr_id"] = rev_reg_index - await txn.handle.replace( - CATEGORY_REV_REG_INFO, revoc_reg_id, value_json=rev_info - ) - await txn.commit() - except AskarError as err: - raise AnonCredsIssuerError( - "Error updating revocation registry index" - ) from err - - revoc = CredentialRevocationConfig( - rev_reg_def, - rev_key.raw_value, - rev_reg.raw_value, - rev_reg_index, - rev_info.get("used_ids") or [], - tails_file_path, - ) - credential_revocation_id = str(rev_reg_index) - else: - revoc = None - credential_revocation_id = None - - try: - ( - credential, - _upd_rev_reg, - _delta, - ) = await asyncio.get_event_loop().run_in_executor( - None, - Credential.create, - cred_def.raw_value, - cred_def_private.raw_value, - credential_offer, - credential_request, - raw_values, - None, - revoc, - ) - except CredxError as err: - raise AnonCredsIssuerError("Error creating credential") from err - - return credential.to_json(), credential_revocation_id - - async def revoke_credentials( - self, - revoc_reg_id: str, - tails_file_path: str, - cred_revoc_ids: Sequence[str], - ) -> Tuple[str, Sequence[str]]: - """ - Revoke a set of credentials in a revocation registry. - - Args: - revoc_reg_id: ID of the revocation registry - tails_file_path: path to the local tails file - cred_revoc_ids: sequences of credential indexes in the revocation registry - - Returns: - Tuple with the combined revocation delta, list of cred rev ids not revoked - - """ - - delta = None - failed_crids = set() - max_attempt = 5 - attempt = 0 - - while True: - attempt += 1 - if attempt >= max_attempt: - raise AnonCredsIssuerError( - "Repeated conflict attempting to update registry" - ) - try: - async with self._profile.session() as session: - rev_reg_def = await session.handle.fetch( - CATEGORY_REV_REG_DEF, revoc_reg_id - ) - rev_reg = await session.handle.fetch(CATEGORY_REV_REG, revoc_reg_id) - rev_reg_info = await session.handle.fetch( - CATEGORY_REV_REG_INFO, revoc_reg_id - ) - if not rev_reg_def: - raise AnonCredsIssuerError( - "Revocation registry definition not found" - ) - if not rev_reg: - raise AnonCredsIssuerError("Revocation registry not found") - if not rev_reg_info: - raise AnonCredsIssuerError("Revocation registry metadata not found") - except AskarError as err: - raise AnonCredsIssuerError( - "Error retrieving revocation registry" - ) from err - - try: - rev_reg_def = RevocationRegistryDefinition.load(rev_reg_def.raw_value) - except CredxError as err: - raise AnonCredsIssuerError( - "Error loading revocation registry definition" - ) from err - - rev_crids = set() - failed_crids = set() - max_cred_num = rev_reg_def.max_cred_num - rev_info = rev_reg_info.value_json - used_ids = set(rev_info.get("used_ids") or []) - - for rev_id in cred_revoc_ids: - rev_id = int(rev_id) - if rev_id < 1 or rev_id > max_cred_num: - LOGGER.error( - "Skipping requested credential revocation" - "on rev reg id %s, cred rev id=%s not in range", - revoc_reg_id, - rev_id, - ) - failed_crids.add(rev_id) - elif rev_id > rev_info["curr_id"]: - LOGGER.warn( - "Skipping requested credential revocation" - "on rev reg id %s, cred rev id=%s not yet issued", - revoc_reg_id, - rev_id, - ) - failed_crids.add(rev_id) - elif rev_id in used_ids: - LOGGER.warn( - "Skipping requested credential revocation" - "on rev reg id %s, cred rev id=%s already revoked", - revoc_reg_id, - rev_id, - ) - failed_crids.add(rev_id) - else: - rev_crids.add(rev_id) - - if not rev_crids: - break - - try: - rev_reg = RevocationRegistry.load(rev_reg.raw_value) - except CredxError as err: - raise AnonCredsIssuerError("Error loading revocation registry") from err - - try: - delta = await asyncio.get_event_loop().run_in_executor( - None, - lambda: rev_reg.update( - rev_reg_def, - None, # issued - list(rev_crids), # revoked - tails_file_path, - ), - ) - except CredxError as err: - raise AnonCredsIssuerError( - "Error updating revocation registry" - ) from err - - try: - async with self._profile.transaction() as txn: - rev_reg_upd = await txn.handle.fetch( - CATEGORY_REV_REG, revoc_reg_id, for_update=True - ) - rev_info_upd = await txn.handle.fetch( - CATEGORY_REV_REG_INFO, revoc_reg_id, for_update=True - ) - if not rev_reg_upd or not rev_reg_info: - LOGGER.warn( - "Revocation registry missing, skipping update: {}", - revoc_reg_id, - ) - delta = None - break - rev_info_upd = rev_info_upd.value_json - if rev_info_upd != rev_info: - # handle concurrent update to the registry by retrying - continue - await txn.handle.replace( - CATEGORY_REV_REG, revoc_reg_id, rev_reg.to_json_buffer() - ) - used_ids.update(rev_crids) - rev_info_upd["used_ids"] = sorted(used_ids) - await txn.handle.replace( - CATEGORY_REV_REG_INFO, revoc_reg_id, value_json=rev_info_upd - ) - await txn.commit() - except AskarError as err: - raise AnonCredsIssuerError("Error saving revocation registry") from err - break - - return ( - delta and delta.to_json(), - [str(rev_id) for rev_id in sorted(failed_crids)], - ) - - async def merge_revocation_registry_deltas( - self, fro_delta: str, to_delta: str - ) -> str: - """ - Merge revocation registry deltas. - - Args: - fro_delta: original delta in JSON format - to_delta: incoming delta in JSON format - - Returns: - Merged delta in JSON format - - """ - - def update(d1, d2): - try: - delta = RevocationRegistryDelta.load(d1) - delta.update_with(d2) - return delta.to_json() - except CredxError as err: - raise AnonCredsIssuerError( - "Error merging revocation registry deltas" - ) from err - - return await asyncio.get_event_loop().run_in_executor( - None, update, fro_delta, to_delta - ) - - async def create_and_store_revocation_registry( - self, - origin_did: str, - cred_def_id: str, - revoc_def_type: str, - tag: str, - max_cred_num: int, - tails_base_path: str, - ) -> Tuple[str, str, str]: - """ - Create a new revocation registry and store it in the wallet. - - Args: - origin_did: the DID issuing the revocation registry - cred_def_id: the identifier of the related credential definition - revoc_def_type: the revocation registry type (default CL_ACCUM) - tag: the unique revocation registry tag - max_cred_num: the number of credentials supported in the registry - tails_base_path: where to store the tails file - issuance_type: optionally override the issuance type - - Returns: - A tuple of the revocation registry ID, JSON, and entry JSON - - """ - try: - async with self._profile.session() as session: - cred_def = await session.handle.fetch(CATEGORY_CRED_DEF, cred_def_id) - except AskarError as err: - raise AnonCredsIssuerError( - "Error retrieving credential definition" - ) from err - if not cred_def: - raise AnonCredsIssuerError( - "Credential definition not found for revocation registry" - ) - - try: - ( - rev_reg_def, - rev_reg_def_private, - rev_reg, - _rev_reg_delta, - ) = await asyncio.get_event_loop().run_in_executor( - None, - lambda: RevocationRegistryDefinition.create( - origin_did, - cred_def.raw_value, - tag, - revoc_def_type, - max_cred_num, - tails_dir_path=tails_base_path, - ), - ) - except CredxError as err: - raise AnonCredsIssuerError("Error creating revocation registry") from err - - rev_reg_def_id = rev_reg_def.id - rev_reg_def_json = rev_reg_def.to_json() - rev_reg_json = rev_reg.to_json() - - try: - async with self._profile.transaction() as txn: - await txn.handle.insert(CATEGORY_REV_REG, rev_reg_def_id, rev_reg_json) - await txn.handle.insert( - CATEGORY_REV_REG_INFO, - rev_reg_def_id, - value_json={"curr_id": 0, "used_ids": []}, - ) - await txn.handle.insert( - CATEGORY_REV_REG_DEF, rev_reg_def_id, rev_reg_def_json - ) - await txn.handle.insert( - CATEGORY_REV_REG_DEF_PRIVATE, - rev_reg_def_id, - rev_reg_def_private.to_json_buffer(), - ) - await txn.commit() - except AskarError as err: - raise AnonCredsIssuerError("Error saving new revocation registry") from err - - return ( - rev_reg_def_id, - rev_reg_def_json, - rev_reg_json, - ) diff --git a/aries_cloudagent/anoncreds/credx/tests/__init__.py b/aries_cloudagent/anoncreds/credx/tests/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/aries_cloudagent/anoncreds/credx/tests/test_cred_issuance.py b/aries_cloudagent/anoncreds/credx/tests/test_cred_issuance.py deleted file mode 100644 index 027c16b94d..0000000000 --- a/aries_cloudagent/anoncreds/credx/tests/test_cred_issuance.py +++ /dev/null @@ -1,341 +0,0 @@ -import json -import tempfile -import pytest - -from asynctest import mock as async_mock, TestCase as AsyncTestCase - -from ....askar.profile import AskarProfileManager -from ....config.injection_context import InjectionContext -from ....ledger.base import BaseLedger -from ....ledger.multiple_ledger.ledger_requests_executor import ( - IndyLedgerRequestsExecutor, -) - -from .. import issuer, holder, verifier - - -TEST_DID = "55GkHamhTU1ZbTbV2ab9DE" -SCHEMA_NAME = "resident" -SCHEMA_VERSION = "1.0" -SCHEMA_TXN = 1234 -SCHEMA_ID = f"{TEST_DID}:2:{SCHEMA_NAME}:{SCHEMA_VERSION}" -CRED_DEF_ID = f"{TEST_DID}:3:CL:{SCHEMA_TXN}:default" -REV_REG_ID = f"{TEST_DID}:4:{CRED_DEF_ID}:CL_ACCUM:0" -CRED_REFT = "attr_0_uuid" -PRES_REQ_NON_REV = { - "name": "pres-request", - "version": "1.0", - "nonce": "1234567890", - "requested_attributes": {CRED_REFT: {"names": ["name", "moniker"]}}, - "requested_predicates": {}, -} -TIMESTAMP = 99999 -PRES_REQ_REV = { - "name": "pres-request", - "version": "1.0", - "nonce": "1234567890", - "requested_attributes": {CRED_REFT: {"names": ["name", "moniker"]}}, - "requested_predicates": {}, - "non_revoked": {"to": TIMESTAMP}, -} - - -@pytest.mark.askar -@pytest.mark.indy_credx -class TestIndyCredxIssuance(AsyncTestCase): - async def setUp(self): - context = InjectionContext(enforce_typing=False) - mock_ledger = async_mock.MagicMock( - get_credential_definition=async_mock.CoroutineMock( - return_value={"value": {}} - ), - get_revoc_reg_delta=async_mock.CoroutineMock( - return_value=( - {"value": {"...": "..."}}, - 1234567890, - ) - ), - ) - mock_ledger.__aenter__ = async_mock.CoroutineMock(return_value=mock_ledger) - self.ledger = mock_ledger - - self.holder_profile = await AskarProfileManager().provision( - context, - { - "name": ":memory:", - "key": await AskarProfileManager.generate_store_key(), - "key_derivation_method": "RAW", - }, - ) - self.issuer_profile = await AskarProfileManager().provision( - context, - { - "name": ":memory:", - "key": await AskarProfileManager.generate_store_key(), - "key_derivation_method": "RAW", - }, - ) - self.issuer_profile._context.injector.bind_instance(BaseLedger, mock_ledger) - self.issuer_profile._context.injector.bind_instance( - IndyLedgerRequestsExecutor, - async_mock.MagicMock( - get_ledger_for_identifier=async_mock.CoroutineMock( - return_value=(None, mock_ledger) - ) - ), - ) - - self.holder = holder.IndyCredxHolder(self.holder_profile) - self.issuer = issuer.IndyCredxIssuer(self.issuer_profile) - self.verifier = verifier.IndyCredxVerifier(self.issuer_profile) - assert "IndyCredxHolder" in str(self.holder) - assert "IndyCredxIssuer" in str(self.issuer) - assert "IndyCredxVerifier" in str(self.verifier) - - async def test_issue_store_non_rev(self): - assert ( - self.issuer.make_schema_id(TEST_DID, SCHEMA_NAME, SCHEMA_VERSION) - == SCHEMA_ID - ) - - (s_id, schema_json) = await self.issuer.create_schema( - TEST_DID, - SCHEMA_NAME, - SCHEMA_VERSION, - ["name", "moniker"], - ) - assert s_id == SCHEMA_ID - schema = json.loads(schema_json) - schema["seqNo"] = SCHEMA_TXN - - assert ( - self.issuer.make_credential_definition_id(TEST_DID, schema, tag="default") - == CRED_DEF_ID - ) - - ( - cd_id, - cred_def_json, - ) = await self.issuer.create_and_store_credential_definition( - TEST_DID, schema, support_revocation=False - ) - assert cd_id == CRED_DEF_ID - assert await self.issuer.credential_definition_in_wallet(cd_id) - cred_def = json.loads(cred_def_json) - - cred_offer_json = await self.issuer.create_credential_offer(cd_id) - cred_offer = json.loads(cred_offer_json) - - cred_req_json, cred_req_meta_json = await self.holder.create_credential_request( - cred_offer, cred_def, TEST_DID - ) - cred_req = json.loads(cred_req_json) - cred_req_meta = json.loads(cred_req_meta_json) - - cred_json, cred_rev_id = await self.issuer.create_credential( - schema, - cred_offer, - cred_req, - {"name": "NAME", "moniker": "MONIKER"}, - revoc_reg_id=None, - tails_file_path=None, - ) - assert cred_rev_id is None - cred_data = json.loads(cred_json) - - cred_id = await self.holder.store_credential(cred_def, cred_data, cred_req_meta) - - found = await self.holder.get_credential(cred_id) - assert found - stored_cred = json.loads(found) - - assert not await self.holder.get_mime_type(cred_id, "name") - - creds = await self.holder.get_credentials(None, None, None) - assert len(creds) == 1 - assert creds[0] == stored_cred - - assert not await self.holder.credential_revoked(self.ledger, cred_id) - - pres_creds = ( - await self.holder.get_credentials_for_presentation_request_by_referent( - PRES_REQ_NON_REV, - None, - 0, - 10, - {}, - ) - ) - assert pres_creds == [ - { - "cred_info": stored_cred, - "interval": None, - "presentation_referents": [CRED_REFT], - } - ] - - pres_json = await self.holder.create_presentation( - PRES_REQ_NON_REV, - { - "requested_attributes": { - CRED_REFT: {"cred_id": cred_id, "revealed": True} - } - }, - {s_id: schema}, - {cd_id: cred_def}, - rev_states=None, - ) - pres = json.loads(pres_json) - - assert await self.verifier.verify_presentation( - PRES_REQ_NON_REV, pres, {s_id: schema}, {cd_id: cred_def}, {}, {} - ) - - await self.holder.delete_credential(cred_id) - - async def test_issue_store_rev(self): - assert ( - self.issuer.make_schema_id(TEST_DID, SCHEMA_NAME, SCHEMA_VERSION) - == SCHEMA_ID - ) - - (s_id, schema_json) = await self.issuer.create_schema( - TEST_DID, - SCHEMA_NAME, - SCHEMA_VERSION, - ["name", "moniker"], - ) - assert s_id == SCHEMA_ID - schema = json.loads(schema_json) - schema["seqNo"] = SCHEMA_TXN - - assert ( - self.issuer.make_credential_definition_id(TEST_DID, schema, tag="default") - == CRED_DEF_ID - ) - - ( - cd_id, - cred_def_json, - ) = await self.issuer.create_and_store_credential_definition( - TEST_DID, schema, support_revocation=True - ) - assert cd_id == CRED_DEF_ID - cred_def = json.loads(cred_def_json) - self.ledger.get_credential_definition.return_value = cred_def - - with tempfile.TemporaryDirectory() as tmp_path: - ( - reg_id, - reg_def_json, - reg_entry_json, - ) = await self.issuer.create_and_store_revocation_registry( - TEST_DID, cd_id, "CL_ACCUM", "0", 10, tmp_path - ) - assert reg_id == REV_REG_ID - reg_def = json.loads(reg_def_json) - reg_entry = json.loads(reg_entry_json) - tails_path = reg_def["value"]["tailsLocation"] - - cred_offer_json = await self.issuer.create_credential_offer(cd_id) - cred_offer = json.loads(cred_offer_json) - - ( - cred_req_json, - cred_req_meta_json, - ) = await self.holder.create_credential_request( - cred_offer, cred_def, TEST_DID - ) - cred_req = json.loads(cred_req_json) - cred_req_meta = json.loads(cred_req_meta_json) - - cred_json, cred_rev_id = await self.issuer.create_credential( - schema, - cred_offer, - cred_req, - {"name": "NAME", "moniker": "MONIKER"}, - revoc_reg_id=reg_id, - tails_file_path=tails_path, - ) - assert cred_rev_id == "1" - cred_data = json.loads(cred_json) - - cred_id = await self.holder.store_credential( - cred_def, - cred_data, - cred_req_meta, - rev_reg_def=reg_def, - ) - - found = await self.holder.get_credential(cred_id) - assert found - stored_cred = json.loads(found) - - creds = await self.holder.get_credentials(None, None, None) - assert len(creds) == 1 - assert creds[0] == stored_cred - - assert not await self.holder.credential_revoked(self.ledger, cred_id) - - pres_creds = ( - await self.holder.get_credentials_for_presentation_request_by_referent( - PRES_REQ_REV, - None, - 0, - 10, - {}, - ) - ) - assert pres_creds == [ - { - "cred_info": stored_cred, - "interval": {"to": TIMESTAMP}, - "presentation_referents": [CRED_REFT], - } - ] - - rev_state_time = 1 - rev_state_json = await self.holder.create_revocation_state( - cred_rev_id, reg_def, reg_entry, rev_state_time, tails_path - ) - rev_state_init = json.loads(rev_state_json) - rev_delta_init = {"ver": "1.0", "value": rev_state_init["rev_reg"]} - - (rev_delta_2_json, skipped_ids) = await self.issuer.revoke_credentials( - reg_id, tails_path, (1,) - ) - assert not skipped_ids - rev_delta_2 = json.loads(rev_delta_2_json) - - merged = await self.issuer.merge_revocation_registry_deltas( - rev_delta_init, rev_delta_2 - ) - - pres_json = await self.holder.create_presentation( - PRES_REQ_REV, - { - "requested_attributes": { - CRED_REFT: { - "cred_id": cred_id, - "revealed": True, - "timestamp": rev_state_time, - } - } - }, - {s_id: schema}, - {cd_id: cred_def}, - rev_states={reg_id: {rev_state_time: rev_state_init}}, - ) - pres = json.loads(pres_json) - - reg_def["txnTime"] = rev_state_time - assert await self.verifier.verify_presentation( - PRES_REQ_REV, - pres, - {s_id: schema}, - {cd_id: cred_def}, - {reg_id: reg_def}, - {reg_id: {rev_state_time: rev_delta_init}}, - ) - - await self.holder.delete_credential(cred_id) diff --git a/aries_cloudagent/anoncreds/credx/verifier.py b/aries_cloudagent/anoncreds/credx/verifier.py deleted file mode 100644 index 941f4092dd..0000000000 --- a/aries_cloudagent/anoncreds/credx/verifier.py +++ /dev/null @@ -1,85 +0,0 @@ -"""Indy-Credx verifier implementation.""" - -import asyncio -import logging - -from indy_credx import CredxError, Presentation - -from ...core.profile import Profile - -from ..verifier import AnonCredsVerifier, PresVerifyMsg - -LOGGER = logging.getLogger(__name__) - - -class IndyCredxVerifier(AnonCredsVerifier): - """Indy-Credx verifier class.""" - - def __init__(self, profile: Profile): - """ - Initialize an IndyCredxVerifier instance. - - Args: - profile: an active profile instance - - """ - self.profile = profile - - async def verify_presentation( - self, - pres_req, - pres, - schemas, - credential_definitions, - rev_reg_defs, - rev_reg_entries, - ) -> (bool, list): - """ - Verify a presentation. - - Args: - pres_req: Presentation request data - pres: Presentation data - schemas: Schema data - credential_definitions: credential definition data - rev_reg_defs: revocation registry definitions - rev_reg_entries: revocation registry entries - """ - - msgs = [] - try: - msgs += self.non_revoc_intervals(pres_req, pres, credential_definitions) - msgs += await self.check_timestamps( - self.profile, pres_req, pres, rev_reg_defs - ) - msgs += await self.pre_verify(pres_req, pres) - except ValueError as err: - s = str(err) - msgs.append(f"{PresVerifyMsg.PRES_VALUE_ERROR.value}::{s}") - LOGGER.error( - f"Presentation on nonce={pres_req['nonce']} " - f"cannot be validated: {str(err)}" - ) - return (False, msgs) - - try: - presentation = Presentation.load(pres) - verified = await asyncio.get_event_loop().run_in_executor( - None, - presentation.verify, - pres_req, - schemas.values(), - credential_definitions.values(), - rev_reg_defs.values(), - rev_reg_entries, - ) - except CredxError as err: - s = str(err) - msgs.append(f"{PresVerifyMsg.PRES_VERIFY_ERROR.value}::{s}") - LOGGER.exception( - f"Validation of presentation on nonce={pres_req['nonce']} " - "failed with error" - ) - verified = False - - return (verified, msgs) diff --git a/aries_cloudagent/anoncreds/sdk/__init__.py b/aries_cloudagent/anoncreds/sdk/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/aries_cloudagent/anoncreds/sdk/error.py b/aries_cloudagent/anoncreds/sdk/error.py deleted file mode 100644 index a79e0a6194..0000000000 --- a/aries_cloudagent/anoncreds/sdk/error.py +++ /dev/null @@ -1,43 +0,0 @@ -"""Indy error handling.""" - -from typing import Type - -from indy.error import IndyError - -from ...core.error import BaseError - - -class IndyErrorHandler: - """Trap IndyError and raise an appropriate LedgerError instead.""" - - def __init__(self, message: str = None, error_cls: Type[BaseError] = BaseError): - """Init the context manager.""" - self.error_cls = error_cls - self.message = message - - def __enter__(self): - """Enter the context manager.""" - return self - - def __exit__(self, err_type, err_value, err_traceback): - """Exit the context manager.""" - if isinstance(err_value, IndyError): - raise IndyErrorHandler.wrap_error( - err_value, self.message, self.error_cls - ) from err_value - - @classmethod - def wrap_error( - cls, - err_value: IndyError, - message: str = None, - error_cls: Type[BaseError] = BaseError, - ) -> BaseError: - """Create an instance of BaseError from an IndyError.""" - err_msg = message or "Exception while performing indy operation" - indy_message = hasattr(err_value, "message") and err_value.message - if indy_message: - err_msg += f": {indy_message}" - err = error_cls(err_msg) - err.__traceback__ = err_value.__traceback__ - return err diff --git a/aries_cloudagent/anoncreds/sdk/holder.py b/aries_cloudagent/anoncreds/sdk/holder.py deleted file mode 100644 index bb21661909..0000000000 --- a/aries_cloudagent/anoncreds/sdk/holder.py +++ /dev/null @@ -1,483 +0,0 @@ -"""Indy SDK holder implementation.""" - -import json -import logging -import re - -from collections import OrderedDict -from typing import Optional, Sequence, Tuple, Union - -import indy.anoncreds -from indy.error import ErrorCode, IndyError - -from ...anoncreds.sdk.wallet_setup import IndyOpenWallet -from ...ledger.base import BaseLedger -from ...storage.indy import IndySdkStorage -from ...storage.error import StorageError, StorageNotFoundError -from ...storage.record import StorageRecord -from ...wallet.error import WalletNotFoundError - -from ..holder import AnonCredsHolder, AnonCredsHolderError - -from .error import IndyErrorHandler -from .util import create_tails_reader - -LOGGER = logging.getLogger(__name__) - - -class IndySdkHolder(AnonCredsHolder): - """Indy-SDK holder implementation.""" - - def __init__(self, wallet: IndyOpenWallet): - """ - Initialize an AnonCredsHolder instance. - - Args: - wallet: IndyOpenWallet instance - - """ - self.wallet = wallet - - async def create_credential_request( - self, credential_offer: dict, credential_definition: dict, holder_did: str - ) -> Tuple[str, str]: - """ - Create a credential request for the given credential offer. - - Args: - credential_offer: The credential offer to create request for - credential_definition: The credential definition to create an offer for - holder_did: the DID of the agent making the request - - Returns: - A tuple of the credential request and credential request metadata - - """ - - with IndyErrorHandler( - "Error when creating credential request", AnonCredsHolderError - ): - ( - credential_request_json, - credential_request_metadata_json, - ) = await indy.anoncreds.prover_create_credential_req( - self.wallet.handle, - holder_did, - json.dumps(credential_offer), - json.dumps(credential_definition), - self.wallet.master_secret_id, - ) - - LOGGER.debug( - "Created credential request. " - "credential_request_json=%s credential_request_metadata_json=%s", - credential_request_json, - credential_request_metadata_json, - ) - - return credential_request_json, credential_request_metadata_json - - async def store_credential( - self, - credential_definition: dict, - credential_data: dict, - credential_request_metadata: dict, - credential_attr_mime_types=None, - credential_id: str = None, - rev_reg_def: dict = None, - ) -> str: - """ - Store a credential in the wallet. - - Args: - credential_definition: Credential definition for this credential - credential_data: Credential data generated by the issuer - credential_request_metadata: credential request metadata generated - by the issuer - credential_attr_mime_types: dict mapping attribute names to (optional) - MIME types to store as non-secret record, if specified - credential_id: optionally override the stored credential id - rev_reg_def: revocation registry definition in json - - Returns: - the ID of the stored credential - - """ - with IndyErrorHandler( - "Error when storing credential in wallet", AnonCredsHolderError - ): - credential_id = await indy.anoncreds.prover_store_credential( - wallet_handle=self.wallet.handle, - cred_id=credential_id, - cred_req_metadata_json=json.dumps(credential_request_metadata), - cred_json=json.dumps(credential_data), - cred_def_json=json.dumps(credential_definition), - rev_reg_def_json=json.dumps(rev_reg_def) if rev_reg_def else None, - ) - - if credential_attr_mime_types: - mime_types = { - attr: credential_attr_mime_types.get(attr) - for attr in credential_data["values"] - if attr in credential_attr_mime_types - } - if mime_types: - record = StorageRecord( - type=AnonCredsHolder.RECORD_TYPE_MIME_TYPES, - value=credential_id, - tags=mime_types, - id=f"{AnonCredsHolder.RECORD_TYPE_MIME_TYPES}::{credential_id}", - ) - indy_stor = IndySdkStorage(self.wallet) - await indy_stor.add_record(record) - - return credential_id - - async def get_credentials(self, start: int, count: int, wql: dict): - """ - Get credentials stored in the wallet. - - Args: - start: Starting index - count: Number of records to return - wql: wql query dict - - """ - - async def fetch(limit): - """Fetch up to limit (default smaller of all remaining or 256) creds.""" - creds = [] - CHUNK = min(record_count, limit or record_count, AnonCredsHolder.CHUNK) - cardinality = min(limit or record_count, record_count) - - with IndyErrorHandler( - "Error fetching credentials from wallet", AnonCredsHolderError - ): - while len(creds) < cardinality: - batch = json.loads( - await indy.anoncreds.prover_fetch_credentials( - search_handle, CHUNK - ) - ) - creds.extend(batch) - if len(batch) < CHUNK: - break - return creds - - with IndyErrorHandler( - "Error when constructing wallet credential query", AnonCredsHolderError - ): - ( - search_handle, - record_count, - ) = await indy.anoncreds.prover_search_credentials( - self.wallet.handle, json.dumps(wql) - ) - - if start > 0: - # must move database cursor manually - await fetch(start) - credentials = await fetch(count) - - await indy.anoncreds.prover_close_credentials_search(search_handle) - - return credentials - - async def get_credentials_for_presentation_request_by_referent( - self, - presentation_request: dict, - referents: Sequence[str], - start: int, - count: int, - extra_query: Optional[dict] = None, - ): - """ - Get credentials stored in the wallet. - - Args: - presentation_request: Valid presentation request from issuer - referents: Presentation request referents to use to search for creds - start: Starting index - count: Maximum number of records to return - extra_query: wql query dict - - """ - - async def fetch(reft, limit): - """Fetch up to limit (default smaller of all remaining or 256) creds.""" - creds = [] - CHUNK = min(AnonCredsHolder.CHUNK, limit or AnonCredsHolder.CHUNK) - - with IndyErrorHandler( - "Error fetching credentials from wallet for presentation request", - AnonCredsHolderError, - ): - while not limit or len(creds) < limit: - batch = json.loads( - await indy.anoncreds.prover_fetch_credentials_for_proof_req( - search_handle, reft, CHUNK - ) - ) - creds.extend(batch) - if len(batch) < CHUNK: - break - return creds - - with IndyErrorHandler( - "Error when constructing wallet credential query", AnonCredsHolderError - ): - search_handle = await ( - indy.anoncreds.prover_search_credentials_for_proof_req( - self.wallet.handle, - json.dumps(presentation_request), - json.dumps(extra_query if extra_query else {}), - ) - ) - - if not referents: - referents = ( - *presentation_request["requested_attributes"], - *presentation_request["requested_predicates"], - ) - creds_dict = OrderedDict() - - try: - for reft in referents: - # must move database cursor manually - if start > 0: - await fetch(reft, start) - credentials = await fetch(reft, count) - - for cred in credentials: - cred_id = cred["cred_info"]["referent"] - if cred_id not in creds_dict: - cred["presentation_referents"] = {reft} - creds_dict[cred_id] = cred - else: - creds_dict[cred_id]["presentation_referents"].add(reft) - finally: - # Always close - await indy.anoncreds.prover_close_credentials_search_for_proof_req( - search_handle - ) - - for cred in creds_dict.values(): - cred["presentation_referents"] = list(cred["presentation_referents"]) - - creds_ordered = tuple( - [ - cred - for cred in sorted( - creds_dict.values(), - key=lambda c: ( - c["cred_info"]["rev_reg_id"] or "", # irrevocable 1st - c["cred_info"][ - "referent" - ], # should be descending by timestamp if we had it - ), - ) - ] - )[:count] - return creds_ordered - - async def get_credential(self, credential_id: str) -> str: - """ - Get a credential stored in the wallet. - - Args: - credential_id: Credential id to retrieve - - """ - try: - credential_json = await indy.anoncreds.prover_get_credential( - self.wallet.handle, credential_id - ) - except IndyError as err: - if err.error_code == ErrorCode.WalletItemNotFound: - raise WalletNotFoundError( - "Credential {} not found in wallet {}".format( - credential_id, self.wallet.name - ) - ) - else: - raise IndyErrorHandler.wrap_error( - err, - f"Error when fetching credential {credential_id}", - AnonCredsHolderError, - ) from err - - return credential_json - - async def credential_revoked( - self, ledger: BaseLedger, credential_id: str, fro: int = None, to: int = None - ) -> bool: - """ - Check ledger for revocation status of credential by cred id. - - Args: - credential_id: Credential id to check - - """ - cred = json.loads(await self.get_credential(credential_id)) - rev_reg_id = cred["rev_reg_id"] - - if rev_reg_id: - cred_rev_id = int(cred["cred_rev_id"]) - (rev_reg_delta, _) = await ledger.get_revoc_reg_delta( - rev_reg_id, - fro, - to, - ) - - return cred_rev_id in rev_reg_delta["value"].get("revoked", []) - else: - return False - - async def delete_credential(self, credential_id: str): - """ - Remove a credential stored in the wallet. - - Args: - credential_id: Credential id to remove - - """ - try: - indy_stor = IndySdkStorage(self.wallet) - mime_types_record = await indy_stor.get_record( - AnonCredsHolder.RECORD_TYPE_MIME_TYPES, - f"{AnonCredsHolder.RECORD_TYPE_MIME_TYPES}::{credential_id}", - ) - await indy_stor.delete_record(mime_types_record) - except StorageNotFoundError: - pass # MIME types record not present: carry on - - try: - await indy.anoncreds.prover_delete_credential( - self.wallet.handle, credential_id - ) - except IndyError as err: - if err.error_code == ErrorCode.WalletItemNotFound: - raise WalletNotFoundError( - "Credential {} not found in wallet {}".format( - credential_id, self.wallet.name - ) - ) - else: - raise IndyErrorHandler.wrap_error( - err, "Error when deleting credential", AnonCredsHolderError - ) from err - - async def get_mime_type( - self, credential_id: str, attr: str = None - ) -> Union[dict, str]: - """ - Get MIME type per attribute (or for all attributes). - - Args: - credential_id: credential id - attr: attribute of interest or omit for all - - Returns: Attribute MIME type or dict mapping attribute names to MIME types - attr_meta_json = all_meta.tags.get(attr) - - """ - try: - mime_types_record = await IndySdkStorage(self.wallet).get_record( - AnonCredsHolder.RECORD_TYPE_MIME_TYPES, - f"{AnonCredsHolder.RECORD_TYPE_MIME_TYPES}::{credential_id}", - ) - except StorageError: - return None # no MIME types: not an error - - return mime_types_record.tags.get(attr) if attr else mime_types_record.tags - - async def create_presentation( - self, - presentation_request: dict, - requested_credentials: dict, - schemas: dict, - credential_definitions: dict, - rev_states: dict = None, - ) -> str: - """ - Get credentials stored in the wallet. - - Args: - presentation_request: Valid indy format presentation request - requested_credentials: Indy format requested credentials - schemas: Indy formatted schemas JSON - credential_definitions: Indy formatted credential definitions JSON - rev_states: Indy format revocation states JSON - - """ - - for reft, spec in presentation_request.get("requested_attributes", {}).items(): - for r in spec.get("restrictions", []): - for k in r: - m = re.match("^attr::(.*)::value$", k) - if not m: - continue - - named_attrs = ( - [spec["name"]] if "name" in spec else spec.get("names", []) - ) - restricted_attr = m.group(1) - if m and restricted_attr not in named_attrs: # wrong attr: hopeless - LOGGER.error( - f"Presentation request {presentation_request['nonce']} " - f"requested attribute {reft} names {named_attrs} " - f"but restricts {restricted_attr} value" - ) - raise AnonCredsHolderError( - f"Requested attribute {reft} names {named_attrs} " - f"but restricts {restricted_attr} value" - ) - - with IndyErrorHandler("Error when constructing proof", AnonCredsHolderError): - presentation_json = await indy.anoncreds.prover_create_proof( - self.wallet.handle, - json.dumps(presentation_request), - json.dumps(requested_credentials), - self.wallet.master_secret_id, - json.dumps(schemas), - json.dumps(credential_definitions), - json.dumps(rev_states) if rev_states else "{}", - ) - - return presentation_json - - async def create_revocation_state( - self, - cred_rev_id: str, - rev_reg_def: dict, - rev_reg_delta: dict, - timestamp: int, - tails_file_path: str, - ) -> str: - """ - Create current revocation state for a received credential. - - Args: - cred_rev_id: credential revocation id in revocation registry - rev_reg_def: revocation registry definition - rev_reg_delta: revocation delta - timestamp: delta timestamp - - Returns: - the revocation state - - """ - - with IndyErrorHandler( - "Error when constructing revocation state", AnonCredsHolderError - ): - tails_file_reader = await create_tails_reader(tails_file_path) - rev_state_json = await indy.anoncreds.create_revocation_state( - tails_file_reader, - rev_reg_def_json=json.dumps(rev_reg_def), - cred_rev_id=cred_rev_id, - rev_reg_delta_json=json.dumps(rev_reg_delta), - timestamp=timestamp, - ) - - return rev_state_json diff --git a/aries_cloudagent/anoncreds/sdk/issuer.py b/aries_cloudagent/anoncreds/sdk/issuer.py deleted file mode 100644 index 74f54de218..0000000000 --- a/aries_cloudagent/anoncreds/sdk/issuer.py +++ /dev/null @@ -1,385 +0,0 @@ -"""Indy SDK issuer implementation.""" - -import json -import logging -from typing import Sequence, Tuple - -import indy.anoncreds -import indy.blob_storage -from indy.error import AnoncredsRevocationRegistryFullError, IndyError, ErrorCode - -from ...anoncreds.sdk.profile import IndySdkProfile -from ...messaging.util import encode -from ...storage.error import StorageError - -from ..issuer import ( - AnonCredsIssuer, - AnonCredsIssuerError, - AnonCredsIssuerRevocationRegistryFullError, - DEFAULT_CRED_DEF_TAG, - DEFAULT_SIGNATURE_TYPE, -) - -from .error import IndyErrorHandler -from .util import create_tails_reader, create_tails_writer - -LOGGER = logging.getLogger(__name__) - - -class IndySdkIssuer(AnonCredsIssuer): - """Indy-SDK issuer implementation.""" - - def __init__(self, profile: IndySdkProfile): - """ - Initialize an AnonCredsIssuer instance. - - Args: - profile: IndySdkProfile instance - - """ - self.profile = profile - - async def create_schema( - self, - origin_did: str, - schema_name: str, - schema_version: str, - attribute_names: Sequence[str], - ) -> Tuple[str, str]: - """ - Create a new credential schema. - - Args: - origin_did: the DID issuing the credential definition - schema_name: the schema name - schema_version: the schema version - attribute_names: a sequence of schema attribute names - - Returns: - A tuple of the schema ID and JSON - - """ - - with IndyErrorHandler("Error when creating schema", AnonCredsIssuerError): - schema_id, schema_json = await indy.anoncreds.issuer_create_schema( - origin_did, - schema_name, - schema_version, - json.dumps(attribute_names), - ) - return (schema_id, schema_json) - - async def credential_definition_in_wallet( - self, credential_definition_id: str - ) -> bool: - """ - Check whether a given credential definition ID is present in the wallet. - - Args: - credential_definition_id: The credential definition ID to check - """ - try: - await indy.anoncreds.issuer_create_credential_offer( - self.profile.wallet.handle, credential_definition_id - ) - return True - except IndyError as err: - if err.error_code not in ( - ErrorCode.CommonInvalidStructure, - ErrorCode.WalletItemNotFound, - ): - raise IndyErrorHandler.wrap_error( - err, - "Error when checking wallet for credential definition", - AnonCredsIssuerError, - ) from err - # recognized error signifies no such cred def in wallet: pass - return False - - async def create_and_store_credential_definition( - self, - origin_did: str, - schema: dict, - signature_type: str = None, - tag: str = None, - support_revocation: bool = False, - ) -> Tuple[str, str]: - """ - Create a new credential definition and store it in the wallet. - - Args: - origin_did: the DID issuing the credential definition - schema: the schema used as a basis - signature_type: the credential definition signature type (default 'CL') - tag: the credential definition tag - support_revocation: whether to enable revocation for this credential def - - Returns: - A tuple of the credential definition ID and JSON - - """ - - with IndyErrorHandler( - "Error when creating credential definition", AnonCredsIssuerError - ): - ( - credential_definition_id, - credential_definition_json, - ) = await indy.anoncreds.issuer_create_and_store_credential_def( - self.profile.wallet.handle, - origin_did, - json.dumps(schema), - tag or DEFAULT_CRED_DEF_TAG, - signature_type or DEFAULT_SIGNATURE_TYPE, - json.dumps({"support_revocation": support_revocation}), - ) - return (credential_definition_id, credential_definition_json) - - async def create_credential_offer(self, credential_definition_id: str) -> str: - """ - Create a credential offer for the given credential definition id. - - Args: - credential_definition_id: The credential definition to create an offer for - - Returns: - The created credential offer - - """ - with IndyErrorHandler( - "Exception when creating credential offer", AnonCredsIssuerError - ): - credential_offer_json = await indy.anoncreds.issuer_create_credential_offer( - self.profile.wallet.handle, credential_definition_id - ) - - return credential_offer_json - - async def create_credential( - self, - schema: dict, - credential_offer: dict, - credential_request: dict, - credential_values: dict, - rev_reg_id: str = None, - tails_file_path: str = None, - ) -> Tuple[str, str]: - """ - Create a credential. - - Args - schema: Schema to create credential for - credential_offer: Credential Offer to create credential for - credential_request: Credential request to create credential for - credential_values: Values to go in credential - rev_reg_id: ID of the revocation registry - tails_file_path: Path to the local tails file - - Returns: - A tuple of created credential and revocation id - - """ - - encoded_values = {} - schema_attributes = schema["attrNames"] - for attribute in schema_attributes: - # Ensure every attribute present in schema to be set. - # Extraneous attribute names are ignored. - try: - credential_value = credential_values[attribute] - except KeyError: - raise AnonCredsIssuerError( - "Provided credential values are missing a value " - + f"for the schema attribute '{attribute}'" - ) - - encoded_values[attribute] = {} - encoded_values[attribute]["raw"] = str(credential_value) - encoded_values[attribute]["encoded"] = encode(credential_value) - - tails_reader_handle = ( - await create_tails_reader(tails_file_path) - if tails_file_path is not None - else None - ) - - try: - ( - credential_json, - cred_rev_id, - _, # rev_reg_delta_json only for ISSUANCE_ON_DEMAND, excluded by design - ) = await indy.anoncreds.issuer_create_credential( - self.profile.wallet.handle, - json.dumps(credential_offer), - json.dumps(credential_request), - json.dumps(encoded_values), - rev_reg_id, - tails_reader_handle, - ) - except AnoncredsRevocationRegistryFullError: - LOGGER.warning( - "Revocation registry %s is full: cannot create credential", - rev_reg_id, - ) - raise AnonCredsIssuerRevocationRegistryFullError( - f"Revocation registry {rev_reg_id} is full" - ) - except IndyError as err: - raise IndyErrorHandler.wrap_error( - err, "Error when issuing credential", AnonCredsIssuerError - ) from err - except StorageError as err: - LOGGER.warning( - ( - "Created issuer cred rev record for " - "Could not store issuer cred rev record for " - "rev reg id %s, cred rev id %s: %s" - ), - rev_reg_id, - cred_rev_id, - err.roll_up, - ) - - return (credential_json, cred_rev_id) - - async def revoke_credentials( - self, - rev_reg_id: str, - tails_file_path: str, - cred_rev_ids: Sequence[str], - ) -> Tuple[str, Sequence[str]]: - """ - Revoke a set of credentials in a revocation registry. - - Args: - rev_reg_id: ID of the revocation registry - tails_file_path: path to the local tails file - cred_rev_ids: sequences of credential indexes in the revocation registry - - Returns: - Tuple with the combined revocation delta, list of cred rev ids not revoked - - """ - failed_crids = set() - tails_reader_handle = await create_tails_reader(tails_file_path) - - result_json = None - for cred_rev_id in set(cred_rev_ids): - with IndyErrorHandler( - "Exception when revoking credential", AnonCredsIssuerError - ): - try: - delta_json = await indy.anoncreds.issuer_revoke_credential( - self.profile.wallet.handle, - tails_reader_handle, - rev_reg_id, - cred_rev_id, - ) - except IndyError as err: - if err.error_code == ErrorCode.AnoncredsInvalidUserRevocId: - LOGGER.error( - ( - "Abstaining from revoking credential on " - "rev reg id %s, cred rev id=%s: " - "already revoked or not yet issued" - ), - rev_reg_id, - cred_rev_id, - ) - else: - LOGGER.error( - IndyErrorHandler.wrap_error( - err, "Revocation error", AnonCredsIssuerError - ).roll_up - ) - failed_crids.add(int(cred_rev_id)) - continue - except StorageError as err: - LOGGER.warning( - ( - "Revoked credential on rev reg id %s, cred rev id %s " - "without corresponding issuer cred rev record: %s" - ), - rev_reg_id, - cred_rev_id, - err.roll_up, - ) - # carry on with delta merge; record is best-effort - - if result_json: - result_json = await self.merge_revocation_registry_deltas( - result_json, delta_json - ) - else: - result_json = delta_json - - return (result_json, [str(rev_id) for rev_id in sorted(failed_crids)]) - - async def merge_revocation_registry_deltas( - self, fro_delta: str, to_delta: str - ) -> str: - """ - Merge revocation registry deltas. - - Args: - fro_delta: original delta in JSON format - to_delta: incoming delta in JSON format - - Returns: - Merged delta in JSON format - - """ - - return await indy.anoncreds.issuer_merge_revocation_registry_deltas( - fro_delta, to_delta - ) - - async def create_and_store_revocation_registry( - self, - origin_did: str, - cred_def_id: str, - revoc_def_type: str, - tag: str, - max_cred_num: int, - tails_base_path: str, - ) -> Tuple[str, str, str]: - """ - Create a new revocation registry and store it in the wallet. - - Args: - origin_did: the DID issuing the revocation registry - cred_def_id: the identifier of the related credential definition - revoc_def_type: the revocation registry type (default CL_ACCUM) - tag: the unique revocation registry tag - max_cred_num: the number of credentials supported in the registry - tails_base_path: where to store the tails file - - Returns: - A tuple of the revocation registry ID, JSON, and entry JSON - - """ - - tails_writer = await create_tails_writer(tails_base_path) - - with IndyErrorHandler( - "Exception when creating revocation registry", AnonCredsIssuerError - ): - ( - rev_reg_id, - rev_reg_def_json, - rev_reg_entry_json, - ) = await indy.anoncreds.issuer_create_and_store_revoc_reg( - self.profile.wallet.handle, - origin_did, - revoc_def_type, - tag, - cred_def_id, - json.dumps( - { - "issuance_type": "ISSUANCE_BY_DEFAULT", - "max_cred_num": max_cred_num, - } - ), - tails_writer, - ) - return (rev_reg_id, rev_reg_def_json, rev_reg_entry_json) diff --git a/aries_cloudagent/anoncreds/sdk/profile.py b/aries_cloudagent/anoncreds/sdk/profile.py deleted file mode 100644 index 109922da08..0000000000 --- a/aries_cloudagent/anoncreds/sdk/profile.py +++ /dev/null @@ -1,198 +0,0 @@ -"""Manage Indy-SDK profile interaction.""" - -import asyncio -import logging - -from typing import Any, Mapping -from weakref import finalize, ref - -from ...config.injection_context import InjectionContext -from ...config.provider import ClassProvider -from ...core.profile import Profile, ProfileManager, ProfileSession -from ...core.error import ProfileError -from ...ledger.base import BaseLedger -from ...ledger.indy import IndySdkLedger, IndySdkLedgerPool -from ...storage.base import BaseStorage, BaseStorageSearch -from ...storage.vc_holder.base import VCHolder -from ...wallet.base import BaseWallet -from ...wallet.indy import IndySdkWallet - -from ..holder import AnonCredsHolder -from ..issuer import AnonCredsIssuer -from ..verifier import AnonCredsVerifier - -from .wallet_setup import IndyWalletConfig, IndyOpenWallet - -LOGGER = logging.getLogger(__name__) - - -class IndySdkProfile(Profile): - """Provide access to Indy profile interaction methods.""" - - BACKEND_NAME = "indy" - - def __init__( - self, - opened: IndyOpenWallet, - context: InjectionContext = None, - ): - """Create a new IndyProfile instance.""" - super().__init__(context=context, name=opened.name, created=opened.created) - self.opened = opened - self.ledger_pool: IndySdkLedgerPool = None - self.init_ledger_pool() - self.bind_providers() - self._finalizer = self._make_finalizer(opened) - - @property - def name(self) -> str: - """Accessor for the profile name.""" - return self.opened.name - - @property - def wallet(self) -> IndyOpenWallet: - """Accessor for the opened wallet instance.""" - return self.opened - - def init_ledger_pool(self): - """Initialize the ledger pool.""" - if self.settings.get("ledger.disabled"): - LOGGER.info("Ledger support is disabled") - return - - if self.settings.get("ledger.genesis_transactions"): - self.ledger_pool = self.context.inject(IndySdkLedgerPool, self.settings) - - def bind_providers(self): - """Initialize the profile-level instance providers.""" - injector = self._context.injector - - injector.bind_provider( - BaseStorageSearch, - ClassProvider("aries_cloudagent.storage.indy.IndySdkStorage", self.opened), - ) - - injector.bind_provider( - AnonCredsHolder, - ClassProvider( - "aries_cloudagent.anoncreds.sdk.holder.IndySdkHolder", self.opened - ), - ) - injector.bind_provider( - AnonCredsIssuer, - ClassProvider( - "aries_cloudagent.anoncreds.sdk.issuer.IndySdkIssuer", ref(self) - ), - ) - - injector.bind_provider( - VCHolder, - ClassProvider( - "aries_cloudagent.storage.vc_holder.indy.IndySdkVCHolder", self.opened - ), - ) - - if self.ledger_pool: - injector.bind_provider( - BaseLedger, ClassProvider(IndySdkLedger, self.ledger_pool, ref(self)) - ) - if self.ledger_pool or self.settings.get("ledger.ledger_config_list"): - injector.bind_provider( - AnonCredsVerifier, - ClassProvider( - "aries_cloudagent.anoncreds.sdk.verifier.IndySdkVerifier", - ref(self), - ), - ) - - def session(self, context: InjectionContext = None) -> "ProfileSession": - """Start a new interactive session with no transaction support requested.""" - return IndySdkProfileSession(self, context=context) - - def transaction(self, context: InjectionContext = None) -> "ProfileSession": - """ - Start a new interactive session with commit and rollback support. - - If the current backend does not support transactions, then commit - and rollback operations of the session will not have any effect. - """ - return IndySdkProfileSession(self, context=context) - - async def close(self): - """Close the profile instance.""" - if self.opened: - await self.opened.close() - self.opened = None - - def _make_finalizer(self, opened: IndyOpenWallet) -> finalize: - """Return a finalizer for this profile. - - See docs for weakref.finalize for more details on behavior of finalizers. - """ - - async def _closer(opened: IndyOpenWallet): - try: - await opened.close() - except Exception: - LOGGER.exception("Failed to close wallet from finalizer") - - def _finalize(opened: IndyOpenWallet): - LOGGER.debug("Profile finalizer called; closing wallet") - asyncio.get_event_loop().create_task(_closer(opened)) - - return finalize(self, _finalize, opened) - - async def remove(self): - """Remove the profile associated with this instance.""" - if not self.opened: - raise ProfileError("Wallet must be opened to remove profile") - - self.opened.config.auto_remove = True - await self.close() - - -class IndySdkProfileSession(ProfileSession): - """An active connection to the profile management backend.""" - - def __init__( - self, - profile: Profile, - *, - context: InjectionContext = None, - settings: Mapping[str, Any] = None - ): - """Create a new IndySdkProfileSession instance.""" - super().__init__(profile=profile, context=context, settings=settings) - - async def _setup(self): - """Create the session or transaction connection, if needed.""" - injector = self._context.injector - injector.bind_provider( - BaseWallet, ClassProvider(IndySdkWallet, self.profile.opened) - ) - injector.bind_provider( - BaseStorage, - ClassProvider( - "aries_cloudagent.storage.indy.IndySdkStorage", self.profile.opened - ), - ) - - -class IndySdkProfileManager(ProfileManager): - """Manager for Indy-SDK wallets.""" - - async def provision( - self, context: InjectionContext, config: Mapping[str, Any] = None - ) -> Profile: - """Provision a new instance of a profile.""" - indy_config = IndyWalletConfig(config) - opened = await indy_config.create_wallet() - return IndySdkProfile(opened, context) - - async def open( - self, context: InjectionContext, config: Mapping[str, Any] = None - ) -> Profile: - """Open an instance of an existing profile.""" - indy_config = IndyWalletConfig(config) - opened = await indy_config.open_wallet() - return IndySdkProfile(opened, context) diff --git a/aries_cloudagent/anoncreds/sdk/tests/__init__.py b/aries_cloudagent/anoncreds/sdk/tests/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/aries_cloudagent/anoncreds/sdk/tests/test_holder.py b/aries_cloudagent/anoncreds/sdk/tests/test_holder.py deleted file mode 100644 index 8403efcd49..0000000000 --- a/aries_cloudagent/anoncreds/sdk/tests/test_holder.py +++ /dev/null @@ -1,604 +0,0 @@ -import json -import pytest - -from asynctest import mock as async_mock, TestCase as AsyncTestCase - -import indy.anoncreds - -from indy.error import IndyError, ErrorCode - -from ...holder import AnonCredsHolder, AnonCredsHolderError - -from .. import holder as test_module - - -@pytest.mark.indy -class TestIndySdkHolder(AsyncTestCase): - def setUp(self): - mock_ledger = async_mock.MagicMock( - get_credential_definition=async_mock.MagicMock(return_value={"value": {}}), - get_revoc_reg_delta=async_mock.CoroutineMock( - return_value=( - {"value": {"...": "..."}}, - 1234567890, - ) - ), - ) - mock_ledger.__aenter__ = async_mock.CoroutineMock(return_value=mock_ledger) - self.ledger = mock_ledger - self.wallet = async_mock.MagicMock() - - self.holder = test_module.IndySdkHolder(self.wallet) - assert "IndySdkHolder" in str(self.holder) - - @async_mock.patch("indy.anoncreds.prover_create_credential_req") - async def test_create_credential_request(self, mock_create_credential_req): - mock_create_credential_req.return_value = ("{}", "[]") - - cred_req_json, cred_req_meta_json = await self.holder.create_credential_request( - "credential_offer", "credential_definition", "did" - ) - - mock_create_credential_req.assert_called_once_with( - self.wallet.handle, - "did", - json.dumps("credential_offer"), - json.dumps("credential_definition"), - self.wallet.master_secret_id, - ) - - assert (json.loads(cred_req_json), json.loads(cred_req_meta_json)) == ({}, []) - - @async_mock.patch("indy.anoncreds.prover_store_credential") - async def test_store_credential(self, mock_store_cred): - mock_store_cred.return_value = "cred_id" - - cred_id = await self.holder.store_credential( - "credential_definition", "credential_data", "credential_request_metadata" - ) - - mock_store_cred.assert_called_once_with( - wallet_handle=self.wallet.handle, - cred_id=None, - cred_req_metadata_json=json.dumps("credential_request_metadata"), - cred_json=json.dumps("credential_data"), - cred_def_json=json.dumps("credential_definition"), - rev_reg_def_json=None, - ) - - assert cred_id == "cred_id" - - @async_mock.patch("indy.anoncreds.prover_store_credential") - async def test_store_credential_with_mime_types(self, mock_store_cred): - with async_mock.patch.object( - test_module, "IndySdkStorage", async_mock.MagicMock() - ) as mock_storage: - mock_storage.return_value = async_mock.MagicMock( - add_record=async_mock.CoroutineMock() - ) - - mock_store_cred.return_value = "cred_id" - - CRED_DATA = {"values": {"cameo": "d29yZCB1cA=="}} - cred_id = await self.holder.store_credential( - "credential_definition", - CRED_DATA, - "credential_request_metadata", - {"cameo": "image/png"}, - ) - - mock_store_cred.assert_called_once_with( - wallet_handle=self.wallet.handle, - cred_id=None, - cred_req_metadata_json=json.dumps("credential_request_metadata"), - cred_json=json.dumps(CRED_DATA), - cred_def_json=json.dumps("credential_definition"), - rev_reg_def_json=None, - ) - mock_storage.return_value.add_record.assert_called_once() - - assert cred_id == "cred_id" - - @async_mock.patch("indy.non_secrets.get_wallet_record") - async def test_get_credential_attrs_mime_types(self, mock_nonsec_get_wallet_record): - cred_id = "credential_id" - dummy_tags = {"a": "1", "b": "2"} - dummy_rec = { - "type": AnonCredsHolder.RECORD_TYPE_MIME_TYPES, - "id": cred_id, - "value": "value", - "tags": dummy_tags, - } - mock_nonsec_get_wallet_record.return_value = json.dumps(dummy_rec) - - mime_types = await self.holder.get_mime_type(cred_id) - - mock_nonsec_get_wallet_record.assert_called_once_with( - self.wallet.handle, - dummy_rec["type"], - f"{AnonCredsHolder.RECORD_TYPE_MIME_TYPES}::{dummy_rec['id']}", - json.dumps( - {"retrieveType": False, "retrieveValue": True, "retrieveTags": True} - ), - ) - - assert mime_types == dummy_tags - - @async_mock.patch("indy.non_secrets.get_wallet_record") - async def test_get_credential_attr_mime_type(self, mock_nonsec_get_wallet_record): - cred_id = "credential_id" - dummy_tags = {"a": "1", "b": "2"} - dummy_rec = { - "type": AnonCredsHolder.RECORD_TYPE_MIME_TYPES, - "id": cred_id, - "value": "value", - "tags": dummy_tags, - } - mock_nonsec_get_wallet_record.return_value = json.dumps(dummy_rec) - - a_mime_type = await self.holder.get_mime_type(cred_id, "a") - - mock_nonsec_get_wallet_record.assert_called_once_with( - self.wallet.handle, - dummy_rec["type"], - f"{AnonCredsHolder.RECORD_TYPE_MIME_TYPES}::{dummy_rec['id']}", - json.dumps( - {"retrieveType": False, "retrieveValue": True, "retrieveTags": True} - ), - ) - - assert a_mime_type == dummy_tags["a"] - - @async_mock.patch("indy.non_secrets.get_wallet_record") - async def test_get_credential_attr_mime_type_x(self, mock_nonsec_get_wallet_record): - cred_id = "credential_id" - dummy_tags = {"a": "1", "b": "2"} - dummy_rec = { - "type": AnonCredsHolder.RECORD_TYPE_MIME_TYPES, - "id": cred_id, - "value": "value", - "tags": dummy_tags, - } - mock_nonsec_get_wallet_record.side_effect = test_module.StorageError() - - assert await self.holder.get_mime_type(cred_id, "a") is None - - @async_mock.patch("indy.anoncreds.prover_search_credentials") - @async_mock.patch("indy.anoncreds.prover_fetch_credentials") - @async_mock.patch("indy.anoncreds.prover_close_credentials_search") - async def test_get_credentials( - self, mock_close_cred_search, mock_fetch_credentials, mock_search_credentials - ): - SIZE = 300 - mock_search_credentials.return_value = ("search_handle", 350) - mock_fetch_credentials.side_effect = [ - json.dumps([0] * test_module.IndySdkHolder.CHUNK), - json.dumps([1] * (SIZE % test_module.IndySdkHolder.CHUNK)), - ] - - credentials = await self.holder.get_credentials(0, SIZE, {}) - mock_search_credentials.assert_called_once_with( - self.wallet.handle, json.dumps({}) - ) - - assert mock_fetch_credentials.call_count == 2 - mock_close_cred_search.assert_called_once_with("search_handle") - - assert len(credentials) == SIZE - - mock_fetch_credentials.side_effect = [ - json.dumps([0] * test_module.IndySdkHolder.CHUNK), - json.dumps([1] * (SIZE % test_module.IndySdkHolder.CHUNK)), - ] - credentials = await self.holder.get_credentials(0, 0, {}) # 0 defaults to all - assert len(credentials) == SIZE - - @async_mock.patch("indy.anoncreds.prover_search_credentials") - @async_mock.patch("indy.anoncreds.prover_fetch_credentials") - @async_mock.patch("indy.anoncreds.prover_close_credentials_search") - async def test_get_credentials_seek( - self, mock_close_cred_search, mock_fetch_credentials, mock_search_credentials - ): - mock_search_credentials.return_value = ("search_handle", 3) - mock_fetch_credentials.return_value = "[1,2,3]" - - credentials = await self.holder.get_credentials(2, 3, {}) - assert mock_fetch_credentials.call_args_list == [ - (("search_handle", 2),), - (("search_handle", 3),), - ] - - @async_mock.patch("indy.anoncreds.prover_search_credentials_for_proof_req") - @async_mock.patch("indy.anoncreds.prover_fetch_credentials_for_proof_req") - @async_mock.patch("indy.anoncreds.prover_close_credentials_search_for_proof_req") - async def test_get_credentials_for_presentation_request_by_reft( - self, - mock_prover_close_credentials_search_for_proof_req, - mock_prover_fetch_credentials_for_proof_req, - mock_prover_search_credentials_for_proof_req, - ): - SIZE = 300 - SKIP = 50 - mock_prover_search_credentials_for_proof_req.return_value = "search_handle" - mock_prover_fetch_credentials_for_proof_req.side_effect = [ - json.dumps( - [ - {"cred_info": {"referent": f"skip-{i}", "rev_reg_id": None}} - for i in range(SKIP) - ] - ), - json.dumps( - [ - { - "cred_info": { - "referent": f"reft-{i}", - "rev_reg_id": None if i % 2 else "dummy-rrid", - } - } - for i in range(test_module.AnonCredsHolder.CHUNK) - ] - ), - json.dumps( - [ - { - "cred_info": { - "referent": f"reft-{test_module.AnonCredsHolder.CHUNK + i}", - "rev_reg_id": None, - } - } - for i in range(SIZE % test_module.AnonCredsHolder.CHUNK) - ] - ), - ] - - PROOF_REQ = { - "requested_attributes": {"attr_0_uuid": {"...": "..."}}, - "requested_predicates": {"pred_0_uuid": {"...": "..."}}, - } - credentials = ( - await self.holder.get_credentials_for_presentation_request_by_referent( - PROOF_REQ, - ("asdb",), - 50, - SIZE, - {"extra": "query"}, - ) - ) - - mock_prover_search_credentials_for_proof_req.assert_called_once_with( - self.wallet.handle, - json.dumps(PROOF_REQ), - json.dumps({"extra": "query"}), - ) - - assert mock_prover_fetch_credentials_for_proof_req.call_count == 3 - mock_prover_close_credentials_search_for_proof_req.assert_called_once_with( - "search_handle" - ) - - assert len(credentials) == SIZE - assert all( - not c["cred_info"]["rev_reg_id"] - for c in credentials[ - 0 : len(credentials) - (test_module.AnonCredsHolder.CHUNK // 2) - ] - ) # irrevocable first - assert all( - c["cred_info"]["rev_reg_id"] - for c in credentials[-test_module.AnonCredsHolder.CHUNK // 2 :] - ) # revocable last - - @async_mock.patch("indy.anoncreds.prover_search_credentials_for_proof_req") - @async_mock.patch("indy.anoncreds.prover_fetch_credentials_for_proof_req") - @async_mock.patch("indy.anoncreds.prover_close_credentials_search_for_proof_req") - async def test_get_credentials_for_presentation_request_by_referent_default_refts( - self, - mock_prover_close_credentials_search_for_proof_req, - mock_prover_fetch_credentials_for_proof_req, - mock_prover_search_credentials_for_proof_req, - ): - mock_prover_search_credentials_for_proof_req.return_value = "search_handle" - mock_prover_fetch_credentials_for_proof_req.return_value = json.dumps( - [{"cred_info": {"referent": "asdb", "rev_reg_id": None}}] - ) - - PRES_REQ = { - "requested_attributes": { - "0_a_uuid": {"...": "..."}, - "1_b_uuid": {"...": "..."}, - }, - "requested_predicates": {"2_c_ge_80": {"...": "..."}}, - } - - credentials = ( - await self.holder.get_credentials_for_presentation_request_by_referent( - PRES_REQ, - None, - 2, - 3, - ) - ) - - mock_prover_search_credentials_for_proof_req.assert_called_once_with( - self.wallet.handle, json.dumps(PRES_REQ), json.dumps({}) - ) - - @async_mock.patch("indy.anoncreds.prover_get_credential") - async def test_get_credential(self, mock_get_cred): - mock_get_cred.return_value = "{}" - credential_json = await self.holder.get_credential("credential_id") - mock_get_cred.assert_called_once_with(self.wallet.handle, "credential_id") - - assert json.loads(credential_json) == {} - - @async_mock.patch("indy.anoncreds.prover_get_credential") - async def test_get_credential_not_found(self, mock_get_cred): - mock_get_cred.side_effect = IndyError(error_code=ErrorCode.WalletItemNotFound) - with self.assertRaises(test_module.WalletNotFoundError): - await self.holder.get_credential("credential_id") - - @async_mock.patch("indy.anoncreds.prover_get_credential") - async def test_get_credential_x(self, mock_get_cred): - mock_get_cred.side_effect = IndyError("unexpected failure") - - with self.assertRaises(test_module.AnonCredsHolderError): - await self.holder.get_credential("credential_id") - - async def test_credential_revoked(self): - with async_mock.patch.object( # no creds revoked - self.holder, "get_credential", async_mock.CoroutineMock() - ) as mock_get_cred: - mock_get_cred.return_value = json.dumps( - { - "rev_reg_id": "dummy-rrid", - "cred_rev_id": "123", - "...": "...", - } - ) - result = await self.holder.credential_revoked(self.ledger, "credential_id") - assert not result - - with async_mock.patch.object( # cred not revocable - self.holder, "get_credential", async_mock.CoroutineMock() - ) as mock_get_cred: - mock_get_cred.return_value = json.dumps( - { - "rev_reg_id": None, - "cred_rev_id": None, - "...": "...", - } - ) - result = await self.holder.credential_revoked(self.ledger, "credential_id") - assert not result - - self.ledger.get_revoc_reg_delta = async_mock.CoroutineMock( - return_value=( - { - "value": { - "revoked": [1, 2, 3], - "...": "...", - } - }, - 1234567890, - ) - ) - with async_mock.patch.object( # cred not revoked - self.holder, "get_credential", async_mock.CoroutineMock() - ) as mock_get_cred: - mock_get_cred.return_value = json.dumps( - { - "rev_reg_id": "dummy-rrid", - "cred_rev_id": "123", - "...": "...", - } - ) - result = await self.holder.credential_revoked(self.ledger, "credential_id") - assert not result - - with async_mock.patch.object( # cred revoked - self.holder, "get_credential", async_mock.CoroutineMock() - ) as mock_get_cred: - mock_get_cred.return_value = json.dumps( - { - "rev_reg_id": "dummy-rrid", - "cred_rev_id": "2", - "...": "...", - } - ) - result = await self.holder.credential_revoked(self.ledger, "credential_id") - assert result - - @async_mock.patch("indy.anoncreds.prover_delete_credential") - @async_mock.patch("indy.non_secrets.get_wallet_record") - @async_mock.patch("indy.non_secrets.delete_wallet_record") - async def test_delete_credential( - self, - mock_nonsec_del_wallet_record, - mock_nonsec_get_wallet_record, - mock_prover_del_cred, - ): - mock_nonsec_get_wallet_record.return_value = json.dumps( - { - "type": "typ", - "id": "ident", - "value": "value", - "tags": {"a": json.dumps("1"), "b": json.dumps("2")}, - } - ) - - credential = await self.holder.delete_credential("credential_id") - - mock_prover_del_cred.assert_called_once_with( - self.wallet.handle, "credential_id" - ) - - @async_mock.patch("indy.anoncreds.prover_delete_credential") - @async_mock.patch("indy.non_secrets.get_wallet_record") - @async_mock.patch("indy.non_secrets.delete_wallet_record") - async def test_delete_credential_x( - self, - mock_nonsec_del_wallet_record, - mock_nonsec_get_wallet_record, - mock_prover_del_cred, - ): - mock_nonsec_get_wallet_record.side_effect = test_module.StorageNotFoundError() - mock_prover_del_cred.side_effect = IndyError( - error_code=ErrorCode.WalletItemNotFound - ) - - with self.assertRaises(test_module.WalletNotFoundError): - await self.holder.delete_credential("credential_id") - mock_prover_del_cred.assert_called_once_with( - self.wallet.handle, "credential_id" - ) - - mock_prover_del_cred.side_effect = IndyError( - error_code=ErrorCode.CommonInvalidParam1 - ) - with self.assertRaises(test_module.AnonCredsHolderError): - await self.holder.delete_credential("credential_id") - assert mock_prover_del_cred.call_count == 2 - - @async_mock.patch("indy.anoncreds.prover_create_proof") - async def test_create_presentation(self, mock_create_proof): - mock_create_proof.return_value = "{}" - PROOF_REQ = { - "nonce": "1554990836", - "name": "proof_req", - "version": "0.0", - "requested_attributes": { - "20_legalname_uuid": { - "name": "legalName", - "restrictions": [ - {"cred_def_id": "WgWxqztrNooG92RXvxSTWv:3:CL:20:tag"} - ], - } - }, - "requested_predicates": { - "21_jurisdictionid_GE_uuid": { - "name": "jurisdictionId", - "p_type": ">=", - "p_value": 1, - "restrictions": [ - {"cred_def_id": "WgWxqztrNooG92RXvxSTWv:3:CL:21:tag"} - ], - } - }, - } - - presentation_json = await self.holder.create_presentation( - PROOF_REQ, - "requested_credentials", - "schemas", - "credential_definitions", - ) - - mock_create_proof.assert_called_once_with( - self.wallet.handle, - json.dumps(PROOF_REQ), - json.dumps("requested_credentials"), - self.wallet.master_secret_id, - json.dumps("schemas"), - json.dumps("credential_definitions"), - json.dumps({}), - ) - - assert json.loads(presentation_json) == {} - - async def test_create_presentation_restr_attr_mismatch_x(self): - PROOF_REQS = [ - { - "nonce": "1554990836", - "name": "proof_req", - "version": "0.0", - "requested_attributes": { - "20_legalname_uuid": { - "name": "legalName", - "restrictions": [ - { - "cred_def_id": "WgWxqztrNooG92RXvxSTWv:3:CL:20:tag", - "attr::wrong::value": "Waffle Asteroid", - } - ], - } - }, - "requested_predicates": { - "21_jurisdictionid_GE_uuid": { - "name": "jurisdictionId", - "p_type": ">=", - "p_value": 1, - "restrictions": [ - {"cred_def_id": "WgWxqztrNooG92RXvxSTWv:3:CL:21:tag"} - ], - } - }, - }, - { - "nonce": "1554990836", - "name": "proof_req", - "version": "0.0", - "requested_attributes": { - "20_legalname_uuid": { - "names": ["legalName", "businessLang"], - "restrictions": [ - { - "cred_def_id": "WgWxqztrNooG92RXvxSTWv:3:CL:20:tag", - "attr::wrong::value": "Waffle Asteroid", - } - ], - } - }, - "requested_predicates": { - "21_jurisdictionid_GE_uuid": { - "name": "jurisdictionId", - "p_type": ">=", - "p_value": 1, - "restrictions": [ - {"cred_def_id": "WgWxqztrNooG92RXvxSTWv:3:CL:21:tag"} - ], - } - }, - }, - ] - - for proof_req in PROOF_REQS: - with self.assertRaises(AnonCredsHolderError): - await self.holder.create_presentation( - proof_req, - "requested_credentials", - "schemas", - "credential_definitions", - ) - - async def test_create_revocation_state(self): - rr_state = { - "witness": {"omega": "1 ..."}, - "rev_reg": {"accum": "21 ..."}, - "timestamp": 1234567890, - } - - with async_mock.patch.object( - test_module, "create_tails_reader", async_mock.CoroutineMock() - ) as mock_create_tails_reader, async_mock.patch.object( - indy.anoncreds, "create_revocation_state", async_mock.CoroutineMock() - ) as mock_create_rr_state: - mock_create_rr_state.return_value = json.dumps(rr_state) - - cred_rev_id = "1" - rev_reg_def = {"def": 1} - rev_reg_delta = {"delta": 1} - timestamp = 1234567890 - tails_path = "/tmp/some.tails" - - result = await self.holder.create_revocation_state( - cred_rev_id, rev_reg_def, rev_reg_delta, timestamp, tails_path - ) - assert json.loads(result) == rr_state - - mock_create_rr_state.assert_awaited_once_with( - mock_create_tails_reader.return_value, - rev_reg_def_json=json.dumps(rev_reg_def), - cred_rev_id=cred_rev_id, - rev_reg_delta_json=json.dumps(rev_reg_delta), - timestamp=timestamp, - ) diff --git a/aries_cloudagent/anoncreds/sdk/tests/test_issuer.py b/aries_cloudagent/anoncreds/sdk/tests/test_issuer.py deleted file mode 100644 index d766fdbdd2..0000000000 --- a/aries_cloudagent/anoncreds/sdk/tests/test_issuer.py +++ /dev/null @@ -1,391 +0,0 @@ -import json -import pytest - -from asynctest import mock as async_mock, TestCase as AsyncTestCase - -from indy.error import ( - AnoncredsRevocationRegistryFullError, - ErrorCode, - IndyError, - WalletItemNotFound, -) - -from ....config.injection_context import InjectionContext -from ....anoncreds.sdk.profile import IndySdkProfile -from ....anoncreds.sdk.wallet_setup import IndyWalletConfig -from ....wallet.indy import IndySdkWallet -from ....ledger.indy import IndySdkLedgerPool - -from ...issuer import AnonCredsIssuerRevocationRegistryFullError - -from .. import issuer as test_module - - -TEST_DID = "55GkHamhTU1ZbTbV2ab9DE" -SCHEMA_NAME = "resident" -SCHEMA_VERSION = "1.0" -SCHEMA_TXN = 1234 -SCHEMA_ID = f"{TEST_DID}:2:{SCHEMA_NAME}:{SCHEMA_VERSION}" -CRED_DEF_ID = f"{TEST_DID}:3:CL:{SCHEMA_TXN}:default" -REV_REG_ID = f"{TEST_DID}:4:{CRED_DEF_ID}:CL_ACCUM:0" -TEST_RR_DELTA = { - "ver": "1.0", - "value": {"prevAccum": "1 ...", "accum": "21 ...", "issued": [1, 2, 12, 42]}, -} - - -@pytest.mark.indy -class TestIndySdkIssuer(AsyncTestCase): - async def setUp(self): - self.context = InjectionContext() - self.context.injector.bind_instance( - IndySdkLedgerPool, IndySdkLedgerPool("name") - ) - - self.wallet = await IndyWalletConfig( - { - "auto_recreate": True, - "auto_remove": True, - "key": await IndySdkWallet.generate_wallet_key(), - "key_derivation_method": "RAW", - "name": "test-wallet", - } - ).create_wallet() - with async_mock.patch.object(IndySdkProfile, "_make_finalizer"): - self.profile = IndySdkProfile(self.wallet, self.context) - self.issuer = test_module.IndySdkIssuer(self.profile) - - async def tearDown(self): - await self.profile.close() - - async def test_repr(self): - assert "IndySdkIssuer" in str(self.issuer) # cover __repr__ - - @async_mock.patch("indy.anoncreds.issuer_create_and_store_credential_def") - async def test_schema_cred_def(self, mock_indy_cred_def): - assert ( - self.issuer.make_schema_id(TEST_DID, SCHEMA_NAME, SCHEMA_VERSION) - == SCHEMA_ID - ) - - (s_id, schema_json) = await self.issuer.create_schema( - TEST_DID, - SCHEMA_NAME, - SCHEMA_VERSION, - ["name", "moniker", "genre", "effective"], - ) - assert s_id == SCHEMA_ID - schema = json.loads(schema_json) - schema["seqNo"] = SCHEMA_TXN - - assert ( - self.issuer.make_credential_definition_id(TEST_DID, schema, tag="default") - == CRED_DEF_ID - ) - - mock_indy_cred_def.return_value = ( - CRED_DEF_ID, - json.dumps({"dummy": "cred-def"}), - ) - assert (CRED_DEF_ID, json.dumps({"dummy": "cred-def"})) == ( - await self.issuer.create_and_store_credential_definition( - TEST_DID, schema, support_revocation=True - ) - ) - - @async_mock.patch("indy.anoncreds.issuer_create_credential_offer") - async def test_credential_definition_in_wallet(self, mock_indy_create_offer): - mock_indy_create_offer.return_value = {"sample": "offer"} - assert await self.issuer.credential_definition_in_wallet(CRED_DEF_ID) - - @async_mock.patch("indy.anoncreds.issuer_create_credential_offer") - async def test_credential_definition_in_wallet_no(self, mock_indy_create_offer): - mock_indy_create_offer.side_effect = WalletItemNotFound( - error_code=ErrorCode.WalletItemNotFound - ) - assert not await self.issuer.credential_definition_in_wallet(CRED_DEF_ID) - - @async_mock.patch("indy.anoncreds.issuer_create_credential_offer") - async def test_credential_definition_in_wallet_x(self, mock_indy_create_offer): - mock_indy_create_offer.side_effect = IndyError( - error_code=ErrorCode.WalletInvalidHandle - ) - with self.assertRaises(test_module.AnonCredsIssuerError): - await self.issuer.credential_definition_in_wallet(CRED_DEF_ID) - - @async_mock.patch("indy.anoncreds.issuer_create_credential_offer") - async def test_create_credential_offer(self, mock_create_offer): - test_offer = {"test": "offer"} - test_cred_def_id = "test-cred-def-id" - mock_create_offer.return_value = json.dumps(test_offer) - mock_profile = async_mock.MagicMock() - issuer = test_module.IndySdkIssuer(mock_profile) - offer_json = await issuer.create_credential_offer(test_cred_def_id) - assert json.loads(offer_json) == test_offer - mock_create_offer.assert_called_once_with( - mock_profile.wallet.handle, test_cred_def_id - ) - - @async_mock.patch("indy.anoncreds.issuer_create_credential") - @async_mock.patch.object(test_module, "create_tails_reader", autospec=True) - @async_mock.patch("indy.anoncreds.issuer_revoke_credential") - @async_mock.patch("indy.anoncreds.issuer_merge_revocation_registry_deltas") - async def test_create_revoke_credentials( - self, - mock_indy_merge_rr_deltas, - mock_indy_revoke_credential, - mock_tails_reader, - mock_indy_create_credential, - ): - test_schema = {"attrNames": ["attr1"]} - test_offer = { - "schema_id": SCHEMA_ID, - "cred_def_id": CRED_DEF_ID, - "key_correctness_proof": {"c": "...", "xz_cap": "...", "xr_cap": ["..."]}, - "nonce": "...", - } - test_request = {"test": "request"} - test_values = {"attr1": "value1"} - test_cred = { - "schema_id": SCHEMA_ID, - "cred_def_id": CRED_DEF_ID, - "rev_reg_id": REV_REG_ID, - "values": {"attr1": {"raw": "value1", "encoded": "123456123899216581404"}}, - "signature": {"...": "..."}, - "signature_correctness_proof": {"...": "..."}, - "rev_reg": {"accum": "21 12E8..."}, - "witness": {"omega": "21 1369..."}, - } - test_cred_rev_ids = ["42", "54"] - test_rr_delta = TEST_RR_DELTA - mock_indy_create_credential.side_effect = [ - ( - json.dumps(test_cred), - cr_id, - test_rr_delta, - ) - for cr_id in test_cred_rev_ids - ] - - with self.assertRaises(test_module.AnonCredsIssuerError): # missing attribute - cred_json, revoc_id = await self.issuer.create_credential( - test_schema, - test_offer, - test_request, - {}, - ) - - (cred_json, cred_rev_id) = await self.issuer.create_credential( # main line - test_schema, - test_offer, - test_request, - test_values, - REV_REG_ID, - "/tmp/tails/path/dummy", - ) - mock_indy_create_credential.assert_called_once() - ( - call_wallet, - call_offer, - call_request, - call_values, - call_etc1, - call_etc2, - ) = mock_indy_create_credential.call_args[0] - assert call_wallet is self.wallet.handle - assert json.loads(call_offer) == test_offer - assert json.loads(call_request) == test_request - values = json.loads(call_values) - assert "attr1" in values - - mock_indy_revoke_credential.return_value = json.dumps(TEST_RR_DELTA) - mock_indy_merge_rr_deltas.return_value = json.dumps(TEST_RR_DELTA) - (result, failed) = await self.issuer.revoke_credentials( - REV_REG_ID, tails_file_path="dummy", cred_rev_ids=test_cred_rev_ids - ) - assert json.loads(result) == TEST_RR_DELTA - assert not failed - assert mock_indy_revoke_credential.call_count == 2 - mock_indy_merge_rr_deltas.assert_called_once() - - @async_mock.patch("indy.anoncreds.issuer_create_credential") - @async_mock.patch.object(test_module, "create_tails_reader", autospec=True) - @async_mock.patch("indy.anoncreds.issuer_revoke_credential") - @async_mock.patch("indy.anoncreds.issuer_merge_revocation_registry_deltas") - async def test_create_revoke_credentials_x( - self, - mock_indy_merge_rr_deltas, - mock_indy_revoke_credential, - mock_tails_reader, - mock_indy_create_credential, - ): - test_schema = {"attrNames": ["attr1"]} - test_offer = { - "schema_id": SCHEMA_ID, - "cred_def_id": CRED_DEF_ID, - "key_correctness_proof": {"c": "...", "xz_cap": "...", "xr_cap": ["..."]}, - "nonce": "...", - } - test_request = {"test": "request"} - test_values = {"attr1": "value1"} - test_cred = { - "schema_id": SCHEMA_ID, - "cred_def_id": CRED_DEF_ID, - "rev_reg_id": REV_REG_ID, - "values": {"attr1": {"raw": "value1", "encoded": "123456123899216581404"}}, - "signature": {"...": "..."}, - "signature_correctness_proof": {"...": "..."}, - "rev_reg": {"accum": "21 12E8..."}, - "witness": {"omega": "21 1369..."}, - } - test_cred_rev_ids = ["42", "54", "103"] - test_rr_delta = TEST_RR_DELTA - mock_indy_create_credential.side_effect = [ - ( - json.dumps(test_cred), - cr_id, - test_rr_delta, - ) - for cr_id in test_cred_rev_ids - ] - - with self.assertRaises(test_module.AnonCredsIssuerError): # missing attribute - cred_json, revoc_id = await self.issuer.create_credential( - test_schema, - test_offer, - test_request, - {}, - ) - - (cred_json, cred_rev_id) = await self.issuer.create_credential( # main line - test_schema, - test_offer, - test_request, - test_values, - REV_REG_ID, - "/tmp/tails/path/dummy", - ) - mock_indy_create_credential.assert_called_once() - ( - call_wallet, - call_offer, - call_request, - call_values, - call_etc1, - call_etc2, - ) = mock_indy_create_credential.call_args[0] - assert call_wallet is self.wallet.handle - assert json.loads(call_offer) == test_offer - assert json.loads(call_request) == test_request - values = json.loads(call_values) - assert "attr1" in values - - def mock_revoke(_h, _t, _r, cred_rev_id): - if cred_rev_id == "42": - return json.dumps(TEST_RR_DELTA) - if cred_rev_id == "54": - raise IndyError( - error_code=ErrorCode.AnoncredsInvalidUserRevocId, - error_details={"message": "already revoked"}, - ) - raise IndyError( - error_code=ErrorCode.UnknownCryptoTypeError, - error_details={"message": "truly an outlier"}, - ) - - mock_indy_revoke_credential.side_effect = mock_revoke - mock_indy_merge_rr_deltas.return_value = json.dumps(TEST_RR_DELTA) - (result, failed) = await self.issuer.revoke_credentials( - REV_REG_ID, tails_file_path="dummy", cred_rev_ids=test_cred_rev_ids - ) - assert json.loads(result) == TEST_RR_DELTA - assert failed == ["54", "103"] - assert mock_indy_revoke_credential.call_count == 3 - mock_indy_merge_rr_deltas.assert_not_called() - - @async_mock.patch("indy.anoncreds.issuer_create_credential") - @async_mock.patch.object(test_module, "create_tails_reader", autospec=True) - async def test_create_credential_rr_full( - self, - mock_tails_reader, - mock_indy_create_credential, - ): - test_schema = {"attrNames": ["attr1"]} - test_offer = { - "schema_id": SCHEMA_ID, - "cred_def_id": CRED_DEF_ID, - "key_correctness_proof": {"c": "...", "xz_cap": "...", "xr_cap": ["..."]}, - "nonce": "...", - } - test_request = {"test": "request"} - test_values = {"attr1": "value1"} - test_credential = {"test": "credential"} - test_cred_rev_id = "42" - test_rr_delta = TEST_RR_DELTA - mock_indy_create_credential.side_effect = AnoncredsRevocationRegistryFullError( - error_code=ErrorCode.AnoncredsRevocationRegistryFullError - ) - - with self.assertRaises(AnonCredsIssuerRevocationRegistryFullError): - await self.issuer.create_credential( - test_schema, - test_offer, - test_request, - test_values, - ) - - @async_mock.patch("indy.anoncreds.issuer_create_credential") - @async_mock.patch.object(test_module, "create_tails_reader", autospec=True) - async def test_create_credential_x_indy( - self, - mock_tails_reader, - mock_indy_create_credential, - ): - test_schema = {"attrNames": ["attr1"]} - test_offer = { - "schema_id": SCHEMA_ID, - "cred_def_id": CRED_DEF_ID, - "key_correctness_proof": {"c": "...", "xz_cap": "...", "xr_cap": ["..."]}, - "nonce": "...", - } - test_request = {"test": "request"} - test_values = {"attr1": "value1"} - test_credential = {"test": "credential"} - test_cred_rev_id = "42" - test_rr_delta = TEST_RR_DELTA - - mock_indy_create_credential.side_effect = IndyError( - error_code=ErrorCode.WalletInvalidHandle - ) - - with self.assertRaises(test_module.AnonCredsIssuerError): - await self.issuer.create_credential( - test_schema, - test_offer, - test_request, - test_values, - ) - - @async_mock.patch("indy.anoncreds.issuer_create_and_store_revoc_reg") - @async_mock.patch.object(test_module, "create_tails_writer", autospec=True) - async def test_create_and_store_revocation_registry( - self, mock_indy_tails_writer, mock_indy_rr - ): - mock_indy_rr.return_value = ("a", "b", "c") - ( - rr_id, - rrdef_json, - rre_json, - ) = await self.issuer.create_and_store_revocation_registry( - TEST_DID, CRED_DEF_ID, "CL_ACCUM", "rr-tag", 100, "/tmp/tails/path" - ) - assert (rr_id, rrdef_json, rre_json) == ("a", "b", "c") - - @async_mock.patch("indy.anoncreds.issuer_merge_revocation_registry_deltas") - async def test_merge_revocation_registry_deltas(self, mock_indy_merge): - mock_indy_merge.return_value = json.dumps({"net": "delta"}) - assert {"net": "delta"} == json.loads( - await self.issuer.merge_revocation_registry_deltas( - {"fro": "delta"}, {"to": "delta"} - ) - ) diff --git a/aries_cloudagent/anoncreds/sdk/tests/test_profile.py b/aries_cloudagent/anoncreds/sdk/tests/test_profile.py deleted file mode 100644 index 8047d4cea6..0000000000 --- a/aries_cloudagent/anoncreds/sdk/tests/test_profile.py +++ /dev/null @@ -1,86 +0,0 @@ -import asyncio -import logging - -from asynctest import mock as async_mock -import pytest - -from ....config.injection_context import InjectionContext -from ....core.error import ProfileError -from ....ledger.indy import IndySdkLedgerPool -from ..profile import IndySdkProfile -from ..wallet_setup import IndyOpenWallet, IndyWalletConfig - - -@pytest.fixture -async def open_wallet(): - opened = IndyOpenWallet( - config=IndyWalletConfig({"name": "test-profile"}), - created=True, - handle=1, - master_secret_id="master-secret", - ) - with async_mock.patch.object(opened, "close", async_mock.CoroutineMock()): - yield opened - - -@pytest.fixture() -async def profile(open_wallet): - context = InjectionContext() - context.injector.bind_instance(IndySdkLedgerPool, IndySdkLedgerPool("name")) - profile = IndySdkProfile(open_wallet, context) - - yield profile - - # Trigger finalizer before event loop fixture is closed - profile._finalizer() - - -@pytest.mark.asyncio -async def test_properties(profile: IndySdkProfile): - assert profile.name == "test-profile" - assert profile.backend == "indy" - assert profile.wallet and profile.wallet.handle == 1 - - assert "IndySdkProfile" in str(profile) - assert profile.created - assert profile.wallet.created - assert profile.wallet.master_secret_id == "master-secret" - - with async_mock.patch.object(profile, "opened", False): - with pytest.raises(ProfileError): - await profile.remove() - - with async_mock.patch.object(profile.opened, "close", async_mock.CoroutineMock()): - await profile.remove() - assert profile.opened is None - - -def test_settings_genesis_transactions(open_wallet): - context = InjectionContext( - settings={"ledger.genesis_transactions": async_mock.MagicMock()} - ) - context.injector.bind_instance(IndySdkLedgerPool, IndySdkLedgerPool("name")) - profile = IndySdkProfile(open_wallet, context) - - -def test_settings_ledger_config(open_wallet): - context = InjectionContext(settings={"ledger.ledger_config_list": True}) - context.injector.bind_instance(IndySdkLedgerPool, IndySdkLedgerPool("name")) - profile = IndySdkProfile(open_wallet, context) - - -def test_read_only(open_wallet): - context = InjectionContext(settings={"ledger.read_only": True}) - context.injector.bind_instance(IndySdkLedgerPool, IndySdkLedgerPool("name")) - ro_profile = IndySdkProfile(open_wallet, context) - - -def test_finalizer(open_wallet, caplog): - def _smaller_scope(): - profile = IndySdkProfile(open_wallet) - assert profile - - with caplog.at_level(logging.DEBUG): - _smaller_scope() - - assert "finalizer called" in caplog.text diff --git a/aries_cloudagent/anoncreds/sdk/tests/test_util.py b/aries_cloudagent/anoncreds/sdk/tests/test_util.py deleted file mode 100644 index be3f5ee36b..0000000000 --- a/aries_cloudagent/anoncreds/sdk/tests/test_util.py +++ /dev/null @@ -1,46 +0,0 @@ -import pytest - -from shutil import rmtree - -import indy.blob_storage - -from asynctest import mock as async_mock, TestCase as AsyncTestCase - -from ...util import indy_client_dir, generate_pr_nonce - -from ..util import create_tails_reader, create_tails_writer - - -@pytest.mark.indy -class TestIndyUtils(AsyncTestCase): - TAILS_HASH = "8UW1Sz5cqoUnK9hqQk7nvtKK65t7Chu3ui866J23sFyJ" - - def tearDown(self): - tails_dir = indy_client_dir("tails", create=False) - rmtree(tails_dir, ignore_errors=True) - - async def test_tails_reader(self): - tails_dir = indy_client_dir("tails", create=True) - tails_local = f"{tails_dir}/{TestIndyUtils.TAILS_HASH}" - - with open(tails_local, "a") as f: - print("1234123412431234", file=f) - - with async_mock.patch.object( - indy.blob_storage, "open_reader", async_mock.CoroutineMock() - ) as mock_blob_open_reader: - result = await create_tails_reader(tails_local) - assert result == mock_blob_open_reader.return_value - - rmtree(tails_dir, ignore_errors=True) - with self.assertRaises(FileNotFoundError): - await create_tails_reader(tails_local) - - async def test_tails_writer(self): - tails_dir = indy_client_dir("tails", create=True) - assert await create_tails_writer(tails_dir) - - rmtree(tails_dir, ignore_errors=True) - - async def test_nonce(self): - assert await generate_pr_nonce() diff --git a/aries_cloudagent/anoncreds/sdk/tests/test_verifier.py b/aries_cloudagent/anoncreds/sdk/tests/test_verifier.py deleted file mode 100644 index d4abc1bdd1..0000000000 --- a/aries_cloudagent/anoncreds/sdk/tests/test_verifier.py +++ /dev/null @@ -1,596 +0,0 @@ -import json -import pytest - -from copy import deepcopy - -from asynctest import mock as async_mock, TestCase as AsyncTestCase -from indy.error import IndyError - -from ....core.in_memory import InMemoryProfile -from ....ledger.multiple_ledger.ledger_requests_executor import ( - IndyLedgerRequestsExecutor, -) - -from ..verifier import IndySdkVerifier - - -INDY_PROOF_REQ_NAME = { - "nonce": "15606741555044336341559", - "name": "proof_req", - "version": "0.0", - "requested_attributes": { - "19_uuid": { - "name": "Preferred Name", - "restrictions": [{"cred_def_id": "LjgpST2rjsoxYegQDRm7EL:3:CL:19:tag"}], - } - }, - "requested_predicates": {}, - "non_revoked": {"from": 1579892963, "to": 1579892963}, -} -INDY_PROOF_NAME = { - "proof": { - "proofs": [ - { - "primary_proof": { - "eq_proof": { - "revealed_attrs": { - "preferredname": "94607763023542937648705576709896212619553924110058781320304650334433495169960" - }, - "a_prime": "...", - "e": "...", - "v": "...", - "m": {"master_secret": "...", "musthave": "..."}, - "m2": "...", - }, - "ge_proofs": [], - }, - "non_revoc_proof": None, - } - ], - "aggregated_proof": {"c_hash": "...", "c_list": [[1, 152, 172, 159]]}, - }, - "requested_proof": { - "revealed_attrs": { - "19_uuid": { - "sub_proof_index": 0, - "raw": "Chicken Hawk", - "encoded": "94607763023542937648705576709896212619553924110058781320304650334433495169960", - } - }, - "self_attested_attrs": {}, - "unrevealed_attrs": {}, - "predicates": {}, - }, - "identifiers": [ - { - "schema_id": "LjgpST2rjsoxYegQDRm7EL:2:non-revo:1579888926.0", - "cred_def_id": "LjgpST2rjsoxYegQDRm7EL:3:CL:19:tag", - "rev_reg_id": "LjgpST2rjsoxYegQDRm7EL:4:LjgpST2rjsoxYegQDRm7EL:3:CL:18:tag:CL_ACCUM:0", - "timestamp": 1579892963, - } - ], -} - -INDY_PROOF_REQ_PRED_NAMES = { - "nonce": "12301197819298309547817", - "name": "proof_req", - "version": "0.0", - "requested_attributes": { - "18_uuid": { - "names": [ - "effectiveDate", - "jurisdictionId", - "endDate", - "legalName", - "orgTypeId", - ], - "restrictions": [{"cred_def_id": "LjgpST2rjsoxYegQDRm7EL:3:CL:18:tag"}], - "non_revoked": {"from": 1579892963, "to": 1579892963}, - } - }, - "requested_predicates": { - "18_id_GE_uuid": { - "name": "id", - "p_type": ">=", - "p_value": 4, - "restrictions": [{"cred_def_id": "LjgpST2rjsoxYegQDRm7EL:3:CL:18:tag"}], - "non_revoked": {"from": 1579892963, "to": 1579892963}, - }, - "18_busid_GE_uuid": { - "name": "busId", - "p_type": ">=", - "p_value": 11198760, - "restrictions": [{"cred_def_id": "LjgpST2rjsoxYegQDRm7EL:3:CL:18:tag"}], - "non_revoked": {"from": 1579892963, "to": 1579892963}, - }, - }, -} - -INDY_PROOF_PRED_NAMES = { - "proof": { - "proofs": [ - { - "primary_proof": { - "eq_proof": { - "revealed_attrs": { - "effectivedate": "29898645652691622994103043707423726726370719600737126045061047957925549204159", - "enddate": "102987336249554097029535212322581322789799900648198034993379397001115665086549", - "jurisdictionid": "1", - "legalname": "106000828387368179355563788886235175190145445419967766011746391966411797095112", - "orgtypeid": "2", - }, - "a_prime": "15004053730967415956444780426929245426212215338862984979494015601906315582840747306504594441147441231491693951307278868750626954115009843921712832446544313561614118651520859494434080523236571958503756731624044004577892061145780190353067193496632483652558392939182437813999901132281095751156851493821923092362225145694407463842363472935374563198989094026343235461171230866928987229440390088485243428084237480758852248249770191814659757536925909483623366615812343227994433513635227834136882757394235805747686707186194818800509367263735891033464810268941516104197138946893490285348940539273855011764023467736767154303840", - "e": "337235637061060569047727820825037317712308782222370290484075504679799877011498224496826887984625822621748120654975531604507028064312710", - "v": "1404574530639210172781710601270953478414552186112985513475784201805119858770941821816370201652610496512142438596496007803868074196519046400754620766301997215969127187833134416898687892635798644651536667962849945968608408680347359937747715599800353850666709655353571249823190377976481837350280859973291095846106817174217510384400072134061086282647508604512946800721425580606901739211324799734725830882957974114794011791236006123406974194631084620463349145771704097181067806553409326685136263002734388842031423620455266365851581883497063570426034222596154597920580362387253753317413050267993785894175555001456331223234400596625730555935567545822248399326429854362227165802672227905967242505077485029657064067770641969647406371744932313880132835781524174868335472062214928455106355639700336515509695339440337721239602256387991397127509846614577908429409389332146746885470613002111095896313068354016587589778644661193149185049", - "m": { - "master_secret": "268741899404098839327031223989228937242803085201179726908056281850709820406283972250249379228789368664433583241086441517910928033266925485611584652328638784395957058632060633630", - "busid": "2687197004064133543257369626470144380098036289489284489320086515026620206692616047425976133587124290887441908383692364439260071404270430528078491104384060203570606253676528361400", - "id": "6686713960986576137959547581149682718587656100042127047852747024276212127252400140409890726173570723819441146289878657604374865560165489933600436341950054222778208816245032311193", - }, - "m2": "1180732317592917288409508571561928260151012766032216949553655321777067495114084046539503538100319204468787213948625648828272873800122130063408401311370987", - }, - "ge_proofs": [ - { - "u": { - "0": "15775609194986735920510151800942995799222803216082415810148803816296803079801357496664353906579826995829149362968465835795491271435248746459334118965204125314594582971550392227954", - "2": "5303150256152520023495176881750201750170184894234097909710215547442554470805609846521764595898911334528530696240025838754931022084938196723161868181531727845300439592437899863887", - "3": "3356711078459696620189646681109895593397921422760359051406583045001333345458592898545852513866307624143916692556089833035405496562577023756005223378326300905996972689863856066875", - "1": "9999991890173781186974768504758157527548652482914116775165195164578745484991479122468109103928320060297494255214338396428491092606606051561499468708339979065194763516537003502062", - }, - "r": { - "DELTA": "1435090146724677611480872211988213747514582597135551797832629955760022689079479873681839403744643599039883834204615937515288097736927712499250203649611222666450687692819628191366070914555251320872315378202337414304735555708434851449005494065128333408295370378734399236857073675785782330461793283646613324794741612075132251003819809779185772616720952264615331933630593162857145006881047266047864525898689246790061753791575361871922643386721142202508891103428155725164118848489256450446385140752308548079412012057535799088600334139468432242004848224338715577616491890083382988533414746224157485737168172255373805848589505587117269746736884630803388115258573174965628402748672653106950672620945656786479587908733067646954", - "2": "1256008392268065460119207279471943768595068414860014149178278435399371400930962253759162888062020269227529923329167742931240473191566641468995735758696802138379814852469042293401437137956018945170092817785505583108837356735852654194156319248319729732466320634812831259869290012647072233952795236462156213752008019954481267917886292498492055846838619027167304191382208021540244250507570410888356448310603088364351895116324030484480320862223729665151544010778941061938440283026451178172286282191179070116776836046514823559685428369094109958537683453915206656040875788707049636999992215238431149080323327641760705766913474027428111260788981734182250598573031877786769378931547452684486997457718460021235476398326984192784", - "0": "1106819115015372998825208031668263762670044285179584053573615157030968633235403572251376628759852167093842839880087972608252236859761641404161906797947710722723839146345785722305071566665021165225324459369096631275993154775947819333058721055644040212303789659749985973470385218248603826878093609862767077416104661216163222886987607841769251824856950498177308519655255200045046063857789306026581362754683975573850037114774186895901788964351934533171525025276070855270632786783019588176816118329221122549398793872640055312900842112041891936494042853798319986365192512964078607266631918748545903216736690057842950922926661694759259530843862322858400156976838479950950178486526234308178957984785053903260967594398611911474", - "3": "1344309321242892215222847471501532826517184846819833677474602495849657271930678291855112591971466462816524573183554788643533109793416521709602069842696124889558288092186793062177006244758779556603409762571362221142413760629539533275654542467194539359678435299002566931998816165917234259226849828723125451685169672272552524344813036153633311318760938874320338280443847065712732394378892985736654998112090834297537844732478643713076998558297751199030671616253345870616092528684635775411928128373368327191277066131632614473951005152162823879892345970535505519113833062530738837915987508410926372810518540478552946670006272356196419957933718303344632112441115930033837912179851905872564389256853587645059720488720795906498", - "1": "601693817301763663113031272722721908754633550776510238958619960119672962844730314111437951375084589705366750107667669458320527173785853103929038523863706300574327283273485302578112396814149563941340189390051835244496748959403476105143355455812570759887645896592114448469303958006046663589996470308366068555479184906610439541373120510006128200782324694975090482529033281708168823833732457689747330091963586305323138559502300486975246644545238909598413498252470653544977963083975726047754021026165970401681664501179243533611966433308438886961268871140737772352678991735861225177227793364352974323624694500485545573621034350559474030565509027433457718466600471361048730018443642651540442802817989514889987171548775560085", - }, - "mj": "2687197004064133543257369626470144380098036289489284489320086515026620206692616047425976133587124290887441908383692364439260071404270430528078491104384060203570606253676528361400", - "alpha": "55264634475788812054149982413198771839810724235465324658821557285735947681415835295178267002738090787902834904063083682990582592095393028970773939852521059447360650213986737569312363077820486616943853008592650537183003498185887824618357246364458614494253289122927160626742649252943244636915456680482390825080294565093068093917997001255757200832353046300646785756652682640188703523223073037006585218839054980180609464837830370157522462983934135435603408143309318659202555550473599548567996557919032937165600303958449173855781262863161799425917680286809410314205550551542955745937751254083650878398344461109371177805333303453760504594222290495116260958547048583654306199387054245295488649024179114894686831993370968945510894767150406222332165620064150891563554498413420757277508788138394747656372783710437243804659113648361274361422790365575", - "t": { - "2": "1276353167840913477021397624773394332173592088650367702185572394040398533199538101776458275797662881371280361310311170677242402214354355702620614537036611922064060504606618126681639882263139365680565350790281701009940301284340534766480451762902788628875609130151618956111512660983755135355570760793108220842022869639781026918247205511538713530652099730605791686827103126406846076633375908411453922078354225032716111673736810973402770388177401531928271370790938081733309345905963052715943136682338494175330354955277424030755355371412956250746882945100461786601740318616758180741835591171045104436982446340050589105952", - "0": "52506109491039096251755479392960889070840776962363540274456217953760113102006029814040519995494713986268145627084927516727099691151450378385140332116480118436738261593744184296007314732823898043080011956933010369575980799348117283597824162615912372823633177749168952698401203464607973674241038357379577293158404669765882906589960120865518413803711729942613061301420107178603192154873722316947550106277771120767826035047479123749931790881679576800340417944013614994751361795012191068369383577242249201927422484806926120532089036692818076818060938822432774203557319821915034796962936855918437128832683302834778450852076", - "1": "113031374658594175812052384858113115052077482873081996361152721528334589441352531310470368095073157716273853401381658707580502108484382463859531044307244944300120928991532655473230562771713806228238940140492981669914382036157400059197253018428984542349187927786210979478008036674432605219414300881116700073904513558719492127462395417843765324361843076852973933175787635618464392198807598044268223652564648024618437362752148593227485835178720349721798423100634521510710239416375840314170338898512726956877281625226003452828033987655579773273571285524048285234475184043290899568731903112287738739915600509899766360789888", - "DELTA": "48234611140928682288937615809872962358698394776719271528059766394227502012090856649758227578113306604028516575292703546202775777621049060595611852517094547384541819122623967215175704296901562660240718967260151010609870475975072516070346770954330313963878747194405855012585768501635077016535846206257741952202337055842434195875166686634891739392750890861333875772954056854354284061793365725202163447856793288398072711194949704852318180275797984445814279136152858759907525062790250675184786142234427994860090376938644764493873560454829155051260697226196758890394411645758956396137763703934929680277278644873416013261035", - "3": "89994605437763910628730772379416923861874648327020237340785010128698483324987645925227420742287632948945347297152300219419713493590120999381541274609870183955909628256613851122899039933589797230083354701292272442523280565440597314568786864750837443422276701528731625877274094155541825495114060437788769205202442723879088098866185978654728309516302335284177924161235100925296934812127598754913984676011716654143603885735997160890946409226842054727795290304131313120189570773196857529159798597569189742987994905034522112705638567954037460125246215182613760404547369876267284411245030884496328403051974209422359756153509", - }, - "predicate": { - "attr_name": "busid", - "p_type": "GE", - "value": 11198760, - }, - }, - { - "u": { - "0": "13639548796026429922431564475630909149287414026758460721805236736313279517016438050089911517098811596997747189614439260518531845477684148307804856579405503329745365642794423965550", - "2": "12692415150154152887167590190910159618471206042982658652940787170770193806407265717354418163057121876574358366510055892372348735991661901637525227498965237677355250159501068181772", - "3": "6699159556719214469836363462599679663866420825429540116943002714507804742697411533141864346616123740789790632843719915716457061440487115732563925309886301301835201778554620543295", - "1": "2018654799729593932888298230804022878883145101317651811950082851492082577094184498971399238402895197739207931768086301073280634251050932415705600476284738694155135236800581664160", - }, - "r": { - "1": "825587756964975640126314737718300012891046538726331178577448524710910340957817679849290109848304786342311186386453239759474660538454793939540876256076287017677140704068118361949660090673111340635478762304690817532764517905140299716866605223450803768338360729151901747687349983483402342999368967231581939563361347289212973086454185400770130710116840233323953976914342262402301362679497329671787598650893202541829399630505463177921655009726556920408538662140155815031458475909120161960047235187953148398737965729023268444789967620657212914775071615366971436269789139928904779054710447116218434690464549160131819794059427689273427325814904354192089075836597740878803445045080385629565176143354201573860707045668850877586", - "2": "1466408189748340763973829793343949568330918709265623621614464341218317503955515434953266875378586538446326464353600075579788794127665478299651259465473747112701101990004860122720151191106445704432013015062973865716673386400413561687311954374930156679604666267815298214479078026652043482916898087471155683856282470644588563159648375551108786970597383143516158031628710096807215305878905007543811401502472821013567629888746492557864905681554913361277548019219082051265255078152509205293776781790132507115787621452248689332496610099725566623311760857590035073594921664074567131690599897210005475078142722295326868452002437292574903183037228401231409631285848202575278151773369676950274790626198680132560950102001994557758", - "0": "993248502537248262082444202395290853332499246354083708269674970707520839045168624341335318664418224639164402187209309139427257892643191846187663592057257899679944076599283980872521437340751206357777926871742796186382563827967273141200749480590415594087209691507734426984052841712131263160951495974745152392404724577427973267669378931113495076274617344076060846279028767371296979484895771867209047720463195305161885422275388748188299814182891315332800557749699941587327916028930365349641271736635219800975554147836564077611147631789530042925759823398087582121686407890628257624663383236878047170688254415445440912626941967028065807021170264150964938678824504194752040131898249057197187446968567390619785928296680096859", - "3": "353677912339120670248802964352055631737613331947764251954000578577314223482877266750851861467829550374246392637478716468616296688578414836737374015352059254057436572686513161681724599053168679581126352074962010335889993562619355121275432902043064229165956511160994192882167562213269670332262473472293819501037932879123080023576285854568501212240875918139761976977842939660466373041805369493971290555885442554468124891943099059169515428968196495673746803133324864149723509564523971808556630671471618581233229134929554792186889060256901637092067130348403992303346483664985586122149628146304160243882639275298266216270358565584574585823864941692911554602002331492551293859949912337984877479524597804956696499812250631744", - "DELTA": "725960022886687948013207416539699149371621853290822104811918058808196468403337509381122781137942343897440199450987104988666229964851227549448628470704889721866971126265999067769808855341632931627785927114398786533559660381398895352266657934136549351825103362166280268159652759301507640976500533521688660251972577237532256300306442315564311264115224457865178259661593100327194825492692234619818096596609477148829377559407992257373097100180145505767561403356284282388735420784241021016181364636135275395790815788682767997871662899508826815736302921531147145381730507095314577476550947092200539059112480501048978059997520366967856033897452966490827003353334313372398949710717623991939354590550708881302450618430658953556", - }, - "mj": "6686713960986576137959547581149682718587656100042127047852747024276212127252400140409890726173570723819441146289878657604374865560165489933600436341950054222778208816245032311193", - "alpha": "54312418620368017823413444392364697511954099195960921763340447211826963863335156427497199363801396644023918819073111783414569539781613083737557451917064836287105910850065875966424715696905777194811070071499968289670886194094119693198719726955718469770568556811780821894560508716495412081426259363632509089906620904790770113618876886851367553577555822830824722182497761114967889600421573310792308390968429341290356015872285765321156360499004114406293720515635636721256956836801168192621092752489119545742530767529595705696014856308531466145146269599634259697543058622520958051728230537251854292098956994695268415292349999637984082162556184322623578612708830627477718675001902228134597558345283147625462346943667586065769392740787755841399970302076200764539143397370091692013055692886714129148712005056929884477612627289722508451690081998890", - "t": { - "3": "76842650074027971332631982512373611181628371639695357946107030911055453488768447213460618917725534086368162318588252003797289252473279448248400376609193928062810022884201102892017821282461806593568305060473753735848560048445524907113838106958747793434918052694775405184619214354190540002998204225798499364075579094271521191419027986291013493577021803670203051346914082929873231509819450163988354047777312127725561922611471445963909565688013793926876707562644935391518355932605047591545917637465241017629839541260483606708345518662351776889719949822005165906622964213143757683950646046295114922019124075069329268061942", - "2": "104991045405164906316724339229643785709360971949973916361929774804163421784479300621496063132861029493850348596359070365652827572699577454378465299784873962729586537933990712981855548459986825452865420618489151243413027040820258308949176618728507177438646401022030966936494703173837070422031040550750643315987178063356959004909489540688791639398005266908038895531691252968451136025538449648159989963830846794193607106472742567850015960071634812903985081979755017126350806404047244177458032873066418448813920685609285163826032405474833353441325867090653794998832828049943461795570528006274431422907140560130037296666626", - "0": "93372553202246858510371387492221683266873274595585378473760313800346458391438909787465170333251843314544241604938410847073082151810448655558028921073676767770394665021417900636520680814177493616534162641758512946743051333557436759523671912141418810442158225543010238061117969558203880853763255647243160765086932831295304550412607848190595598510980669944139696363322475177492264636536910201776020324858798972778323663795303339939472573927415127166116444898790357846635883222746031584554927383535016321617425087697872601850303134185636960112124926520878185699975818343081756286170638877967660840814776000077787223928056", - "1": "88156501142569212422367853754801651086852287000049991938144173063936879655987557042149874374683234911554850776079721311154420204826376746982087019508277766132575858575556680538019849786965963718649479179859820106973881788608463705644074554956818391872137271784803047333543321479251515998725336896820211102747123583803854741248907240437683401575881169746704849524328003061107258995982062254548684849294595639491104266371155934951313704136302996039897528196270331875472554549327417349243990461246127383357748906616773662459665620147625796186736530089927957522542298814250937114283836911153790542409683746775259226224961", - "DELTA": "6178997688515360528852083990605883033892934661031543684879979804577432521872124008044788245406591933749401429548633356472853716766388636618335206416158216292785839570827245139150787585027801572977051847786797012358936548986405917266204321163760568135873831346087680040040301251630530064206552793273933549993844498438010903850013120770715837075880286264742500598494248510064600863411203212869270221192303957773402376357672080257393369434247825409313396018869267942811592657266119556377402842108726474978400793026037873416208879964428023321485607453655856252140587803891157033568210852205447175844430607889546700526279", - }, - "predicate": { - "attr_name": "id", - "p_type": "GE", - "value": 4, - }, - }, - ], - }, - "non_revoc_proof": { - "x_list": { - "rho": "12B28F49BF5F2CDA105B904CD594EB5B5F558025CDDB7D0F3057D19830F81694", - "r": "010F2B872DC4BECAE085D9FA1FB98184C3E00181A678F2B256140482B4DEDFCE", - "r_prime": "1AAF1AB0071B64FE22AC42219962B9ABA02147B09DFDC4C1FD088E6706D312FC", - "r_prime_prime": "1630D0800ADE33816BCA96EE89EC1E312BE80220510FAFAAC52BED567B187017", - "r_prime_prime_prime": "14D06B2F7B369880821DAAFD40D74FE3B495EE3A7CB7E937FDC4E9707330F301", - "o": "147F16718A0CCB2EC137ECA3952C801FB2281B06CB4ADC24092CE31CA2EAC5AD", - "o_prime": "14F4668810341353E0C25485C4F3CF459BCB69DD74FF73376A23ACAA550E09C5", - "m": "0EAC754EE9AC81C495AC3BB302511034965965AF337BC4F43850A1636E70314E", - "m_prime": "07CA764055E9920E7C4417C3A63BF610C145F25D618A41DAC785210D7320F0EF", - "t": "199D84A1133FB103E4E77CC6F917A12355AD4A29DCCC24999A2C67EBD62B5306", - "t_prime": "07154F39841E3D75E1E07179875B94D655E3BDD4A349E0BBAA43422CC527AACB", - "m2": "0D4380FF8ACDC21611BC8AB83127950441DA42A49A347BEC6F32044F033D3017", - "s": "0A65AE9D0C0D4CDAA5D4EECB48BC6DFD2279BD2C040AC0D9011918A9E0A7A866", - "c": "0ABFC02DDF76995C48CADEE8447665EB933446FEC42E7074FB11720E141CFC07", - }, - "c_list": { - "e": "6 418D8713ED93CD8C065EA42D110C581C2CE07A58771077B1C2016E53AA2E7461 4 2032A4917D0877B9723CDCD82B32AC96C534B0CAA5ED2EE3FFD605214511CB1F 4 0D8E5DA074651A0DE91F86F663F071EA4D4CD4CBA438F7A4D182C8D23D01B485", - "d": "6 37635F35298D224C0E3F01EB06DC8AC1D8A7E048027162077E0204801F22FF94 4 1E64440E13B08BD249B5C35B637C70BDA471926F5F3896400ED25EDA4678B73D 4 3A5BB704B473894CD54C91D1D159A7BD8FA8092545F93D1BC195D52D3EC96EDE", - "a": "6 6000DC780B9D7C71575A328DE2BACB78A2737E9C1CE64BC8BCE98BD8486EAAB4 4 39555F38DB15EC820DA3A7A61820F831A003D414D4A0EF60D1D37ABD8B5E1070 4 25FBA1AD320F02D9082118E978B4FE261280951BCE1FED15F65771AE04F8E270", - "g": "6 5D293948EF43989ACBB8262B8C7F10A551AD71190D70B9AAA62943C4FE6A4C42 4 2B3E1ED0A00163DCA9AD9B69DDA124290CF8F580F60595B5E9D506A3C0D9A903 4 29C2B6F7AD7F7B223FC40BD9C1147FCE831B26ACB477855C9D3EABD7B0341342", - "w": "21 1371C921AE2377A1CD9F0D3E863B09487B6DFC0DC5F2BA32133E4F5EF2ACA5641 21 10B84BA9167755980B1DCD97AB698D56E3C9CDCBE7A85F0776A2C96B3BE9519BE 6 6676ADACEC607381F87211DAE4DE6A630B74FAF580DBC383D8450C7852BC09C4 4 379C9A4FF46DEBF21223823D5B2323F7A56A394157E94DB95914A9E5BB27FAEC 6 7121D621C85D9BA22371A0862909FF25198F0EF690207AEE3910FB0E0A7A4F62 4 1C052A0276360F0D8AEBA71BD65ECB233FFDB700F031EA03146CF00BC2F2D5B6", - "s": "21 1272F477F5A0F83CCB316DA088F6A6A12C131D0DC9BC699023F534624B8EE255A 21 13816855011465BE2E8972F52EE4692873A763513A764BD92B8B7CBBBAA27D7E8 6 7B190F599B5F0EA53802135BBD655B080743FE60CC22329F69770D6B765F0AAA 4 2AAA191CA59348C6A920BD1D1AE37A7C96F424B6D8E921B54EA5C1C7C56297AA 6 80254CA5DFBAD3C39BC757534922FBD0846AB86500D5D168109EB6B8A9D2BE33 4 1CC93B3769A7BE2AF52CCE391D2BB57F9D907F530038EF84B3EC4AB54D62D872", - "u": "21 11E538813B74EFC8676EF5AC87AA05A0FF58913B7C68E264FCF5ED0D57F6BC781 21 12EE7BE65E15CF4C500E2D92DB02670FBD8B51C6BD0B35AE139E9CE9658B15CC2 6 856B3C0C152F75A449AD73DFAD7DFD87A99AAA606E3D8E392D765E3C987D7B47 4 34245F01BD7C4144DBEBE7AB35303BF02FB5717EC6B080BC9C2C930D929D4ED7 6 8113F127D8762B174DCB32AEE370297BF7CFCCF797510B53D53564AEC9105909 4 3B2434AD9AB4E7ABA7125800D14470A098AE04FA523CB60A6FFF62D371B95E13", - }, - }, - } - ], - "aggregated_proof": { - "c_hash": "37672016063516849654668323232510746418703126727195560560658262517075578769045", - "c_list": [ - [4, 0, 0, 0, 0, 0], - [4, 17, 153, 0, 0, 0, 0], - [4, 0, 0, 0, 0, 0, 0, 0], - [4, 1, 134, 126, 0, 0, 0, 0], - [10, 250, 248, 125, 158, 54, 165, 91, 59, 1], - [4, 167, 169, 22, 44], - [31, 254, 53], - [118, 218, 1, 27, 51, 96], - [1, 254, 120, 236], - [3, 127, 97, 134, 148, 32, 128], - [10, 124, 191, 32], - [32, 59, 96, 254, 165], - [195, 171, 64, 72, 40, 235], - [2, 175, 185, 172, 248], - [2, 152, 166, 185, 65], - [3, 63, 176, 24, 2], - [2, 96, 182, 196, 220, 182, 246], - [48, 242, 116, 58, 18, 199], - ], - }, - }, - "requested_proof": { - "revealed_attrs": {}, - "revealed_attr_groups": { - "18_uuid": { - "sub_proof_index": 0, - "values": { - "effectiveDate": { - "raw": "2018-01-01", - "encoded": "29898645652691622994103043707423726726370719600737126045061047957925549204159", - }, - "endDate": { - "raw": "", - "encoded": "102987336249554097029535212322581322789799900648198034993379397001115665086549", - }, - "jurisdictionId": {"raw": "1", "encoded": "1"}, - "legalName": { - "raw": "Flan Nebula", - "encoded": "106000828387368179355563788886235175190145445419967766011746391966411797095112", - }, - "orgTypeId": {"raw": "2", "encoded": "2"}, - }, - } - }, - "self_attested_attrs": {}, - "unrevealed_attrs": {}, - "predicates": { - "18_busid_GE_uuid": {"sub_proof_index": 0}, - "18_id_GE_uuid": {"sub_proof_index": 0}, - }, - }, - "identifiers": [ - { - "schema_id": "LjgpST2rjsoxYegQDRm7EL:2:bc-reg:1.0", - "cred_def_id": "LjgpST2rjsoxYegQDRm7EL:3:CL:18:tag", - "rev_reg_id": "LjgpST2rjsoxYegQDRm7EL:4:LjgpST2rjsoxYegQDRm7EL:3:CL:18:tag:CL_ACCUM:0", - "timestamp": 1579892963, - } - ], -} - -REV_REG_DEFS = { - "LjgpST2rjsoxYegQDRm7EL:4:LjgpST2rjsoxYegQDRm7EL:3:CL:18:tag:CL_ACCUM:0": { - "txnTime": 1500000000 - } -} - - -@pytest.mark.indy -class TestIndySdkVerifier(AsyncTestCase): - def setUp(self): - self.ledger = async_mock.MagicMock( - get_credential_definition=async_mock.CoroutineMock( - return_value={ - "...": "...", - "value": { - "revocation": { - "g": "1 ...", - "g_dash": "1 ...", - "h": "1 ...", - "h0": "1 ...", - "h1": "1 ...", - "h2": "1 ...", - "htilde": "1 ...", - "h_cap": "1 ...", - "u": "1 ...", - "pk": "1 ...", - "y": "1 ...", - } - }, - } - ) - ) - mock_profile = InMemoryProfile.test_profile() - context = mock_profile.context - context.injector.bind_instance( - IndyLedgerRequestsExecutor, IndyLedgerRequestsExecutor(mock_profile) - ) - self.verifier = IndySdkVerifier(mock_profile) - assert repr(self.verifier) == "" - - @async_mock.patch("indy.anoncreds.verifier_verify_proof") - async def test_verify_presentation(self, mock_verify): - mock_verify.return_value = "val" - - with async_mock.patch.object( - self.verifier, "pre_verify", async_mock.CoroutineMock() - ) as mock_pre_verify, async_mock.patch.object( - self.verifier, "non_revoc_intervals", async_mock.MagicMock() - ) as mock_non_revox, async_mock.patch.object( - IndyLedgerRequestsExecutor, "get_ledger_for_identifier" - ) as mock_get_ledger: - mock_get_ledger.return_value = (None, self.ledger) - INDY_PROOF_REQ_X = deepcopy(INDY_PROOF_REQ_PRED_NAMES) - (verified, msgs) = await self.verifier.verify_presentation( - INDY_PROOF_REQ_X, - INDY_PROOF_PRED_NAMES, - "schemas", - {"LjgpST2rjsoxYegQDRm7EL:3:CL:18:tag": {"value": {"revocation": {}}}}, - REV_REG_DEFS, - "rev_reg_entries", - ) - - mock_verify.assert_called_once_with( - json.dumps(INDY_PROOF_REQ_X), - json.dumps(INDY_PROOF_PRED_NAMES), - json.dumps("schemas"), - json.dumps( - {"LjgpST2rjsoxYegQDRm7EL:3:CL:18:tag": {"value": {"revocation": {}}}} - ), - json.dumps(REV_REG_DEFS), - json.dumps("rev_reg_entries"), - ) - - assert verified == "val" - - @async_mock.patch("indy.anoncreds.verifier_verify_proof") - async def test_verify_presentation_x_indy(self, mock_verify): - mock_verify.side_effect = IndyError(error_code=1) - - with async_mock.patch.object( - self.verifier, "pre_verify", async_mock.CoroutineMock() - ) as mock_pre_verify, async_mock.patch.object( - self.verifier, "non_revoc_intervals", async_mock.MagicMock() - ) as mock_non_revox, async_mock.patch.object( - IndyLedgerRequestsExecutor, "get_ledger_for_identifier" - ) as mock_get_ledger: - mock_get_ledger.return_value = ("test", self.ledger) - (verified, msgs) = await self.verifier.verify_presentation( - INDY_PROOF_REQ_NAME, - INDY_PROOF_NAME, - "schemas", - {"LjgpST2rjsoxYegQDRm7EL:3:CL:19:tag": {"value": {}}}, - REV_REG_DEFS, - "rev_reg_entries", - ) - - mock_verify.assert_called_once_with( - json.dumps(INDY_PROOF_REQ_NAME), - json.dumps(INDY_PROOF_NAME), - json.dumps("schemas"), - json.dumps({"LjgpST2rjsoxYegQDRm7EL:3:CL:19:tag": {"value": {}}}), - json.dumps(REV_REG_DEFS), - json.dumps("rev_reg_entries"), - ) - - assert not verified - - @async_mock.patch("indy.anoncreds.verifier_verify_proof") - async def test_check_encoding_attr(self, mock_verify): - with async_mock.patch.object( - IndyLedgerRequestsExecutor, "get_ledger_for_identifier" - ) as mock_get_ledger: - mock_get_ledger.return_value = (None, self.ledger) - mock_verify.return_value = True - (verified, msgs) = await self.verifier.verify_presentation( - INDY_PROOF_REQ_NAME, - INDY_PROOF_NAME, - "schemas", - {"LjgpST2rjsoxYegQDRm7EL:3:CL:19:tag": {"value": {}}}, - REV_REG_DEFS, - "rev_reg_entries", - ) - - mock_verify.assert_called_once_with( - json.dumps(INDY_PROOF_REQ_NAME), - json.dumps(INDY_PROOF_NAME), - json.dumps("schemas"), - json.dumps({"LjgpST2rjsoxYegQDRm7EL:3:CL:19:tag": {"value": {}}}), - json.dumps(REV_REG_DEFS), - json.dumps("rev_reg_entries"), - ) - assert verified is True - assert len(msgs) == 1 - assert "TS_OUT_NRI::19_uuid" in msgs - - @async_mock.patch("indy.anoncreds.verifier_verify_proof") - async def test_check_encoding_attr_tamper_raw(self, mock_verify): - INDY_PROOF_X = deepcopy(INDY_PROOF_NAME) - INDY_PROOF_X["requested_proof"]["revealed_attrs"]["19_uuid"][ - "raw" - ] = "Mock chicken" - with async_mock.patch.object( - IndyLedgerRequestsExecutor, "get_ledger_for_identifier" - ) as mock_get_ledger: - mock_get_ledger.return_value = ("test", self.ledger) - (verified, msgs) = await self.verifier.verify_presentation( - INDY_PROOF_REQ_NAME, - INDY_PROOF_X, - "schemas", - {"LjgpST2rjsoxYegQDRm7EL:3:CL:19:tag": {"value": {}}}, - REV_REG_DEFS, - "rev_reg_entries", - ) - - mock_verify.assert_not_called() - - assert verified is False - assert len(msgs) == 2 - assert "TS_OUT_NRI::19_uuid" in msgs - assert ( - "VALUE_ERROR::Encoded representation mismatch for 'Preferred Name'" in msgs - ) - - @async_mock.patch("indy.anoncreds.verifier_verify_proof") - async def test_check_encoding_attr_tamper_encoded(self, mock_verify): - INDY_PROOF_X = deepcopy(INDY_PROOF_NAME) - INDY_PROOF_X["requested_proof"]["revealed_attrs"]["19_uuid"][ - "encoded" - ] = "1234567890" - with async_mock.patch.object( - IndyLedgerRequestsExecutor, "get_ledger_for_identifier" - ) as mock_get_ledger: - mock_get_ledger.return_value = (None, self.ledger) - (verified, msgs) = await self.verifier.verify_presentation( - INDY_PROOF_REQ_NAME, - INDY_PROOF_X, - "schemas", - {"LjgpST2rjsoxYegQDRm7EL:3:CL:19:tag": {"value": {}}}, - REV_REG_DEFS, - "rev_reg_entries", - ) - - mock_verify.assert_not_called() - - assert verified is False - assert len(msgs) == 2 - assert "TS_OUT_NRI::19_uuid" in msgs - assert ( - "VALUE_ERROR::Encoded representation mismatch for 'Preferred Name'" in msgs - ) - - @async_mock.patch("indy.anoncreds.verifier_verify_proof") - async def test_check_pred_names(self, mock_verify): - with async_mock.patch.object( - IndyLedgerRequestsExecutor, "get_ledger_for_identifier" - ) as mock_get_ledger: - mock_get_ledger.return_value = ("test", self.ledger) - mock_verify.return_value = True - INDY_PROOF_REQ_X = deepcopy(INDY_PROOF_REQ_PRED_NAMES) - (verified, msgs) = await self.verifier.verify_presentation( - INDY_PROOF_REQ_X, - INDY_PROOF_PRED_NAMES, - "schemas", - {"LjgpST2rjsoxYegQDRm7EL:3:CL:18:tag": {"value": {"revocation": {}}}}, - REV_REG_DEFS, - "rev_reg_entries", - ) - - mock_verify.assert_called_once_with( - json.dumps(INDY_PROOF_REQ_X), - json.dumps(INDY_PROOF_PRED_NAMES), - json.dumps("schemas"), - json.dumps( - {"LjgpST2rjsoxYegQDRm7EL:3:CL:18:tag": {"value": {"revocation": {}}}} - ), - json.dumps(REV_REG_DEFS), - json.dumps("rev_reg_entries"), - ) - - assert verified is True - assert len(msgs) == 3 - assert "TS_OUT_NRI::18_uuid" in msgs - assert "TS_OUT_NRI::18_id_GE_uuid" in msgs - assert "TS_OUT_NRI::18_busid_GE_uuid" in msgs - - @async_mock.patch("indy.anoncreds.verifier_verify_proof") - async def test_check_pred_names_tamper_pred_value(self, mock_verify): - INDY_PROOF_X = deepcopy(INDY_PROOF_PRED_NAMES) - INDY_PROOF_X["proof"]["proofs"][0]["primary_proof"]["ge_proofs"][0][ - "predicate" - ]["value"] = 0 - with async_mock.patch.object( - IndyLedgerRequestsExecutor, "get_ledger_for_identifier" - ) as mock_get_ledger: - mock_get_ledger.return_value = (None, self.ledger) - (verified, msgs) = await self.verifier.verify_presentation( - deepcopy(INDY_PROOF_REQ_PRED_NAMES), - INDY_PROOF_X, - "schemas", - {"LjgpST2rjsoxYegQDRm7EL:3:CL:18:tag": {"value": {}}}, - REV_REG_DEFS, - "rev_reg_entries", - ) - - mock_verify.assert_not_called() - - assert verified is False - assert len(msgs) == 4 - assert "RMV_RFNT_NRI::18_uuid" in msgs - assert "RMV_RFNT_NRI::18_busid_GE_uuid" in msgs - assert "RMV_RFNT_NRI::18_id_GE_uuid" in msgs - assert ( - "VALUE_ERROR::Timestamp on sub-proof #0 is superfluous vs. requested attribute group 18_uuid" - in msgs - ) - - @async_mock.patch("indy.anoncreds.verifier_verify_proof") - async def test_check_pred_names_tamper_pred_req_attr(self, mock_verify): - INDY_PROOF_REQ_X = deepcopy(INDY_PROOF_REQ_PRED_NAMES) - INDY_PROOF_REQ_X["requested_predicates"]["18_busid_GE_uuid"]["name"] = "dummy" - with async_mock.patch.object( - IndyLedgerRequestsExecutor, "get_ledger_for_identifier" - ) as mock_get_ledger: - mock_get_ledger.return_value = (None, self.ledger) - (verified, msgs) = await self.verifier.verify_presentation( - INDY_PROOF_REQ_X, - INDY_PROOF_PRED_NAMES, - "schemas", - {"LjgpST2rjsoxYegQDRm7EL:3:CL:18:tag": {"value": {}}}, - REV_REG_DEFS, - "rev_reg_entries", - ) - - mock_verify.assert_not_called() - - assert verified is False - assert len(msgs) == 4 - assert "RMV_RFNT_NRI::18_uuid" in msgs - assert "RMV_RFNT_NRI::18_busid_GE_uuid" in msgs - assert "RMV_RFNT_NRI::18_id_GE_uuid" in msgs - assert ( - "VALUE_ERROR::Timestamp on sub-proof #0 is superfluous vs. requested attribute group 18_uuid" - in msgs - ) - - @async_mock.patch("indy.anoncreds.verifier_verify_proof") - async def test_check_pred_names_tamper_attr_groups(self, mock_verify): - INDY_PROOF_X = deepcopy(INDY_PROOF_PRED_NAMES) - INDY_PROOF_X["requested_proof"]["revealed_attr_groups"][ - "x_uuid" - ] = INDY_PROOF_X["requested_proof"]["revealed_attr_groups"].pop("18_uuid") - with async_mock.patch.object( - IndyLedgerRequestsExecutor, "get_ledger_for_identifier" - ) as mock_get_ledger: - mock_get_ledger.return_value = ("test", self.ledger) - (verified, msgs) = await self.verifier.verify_presentation( - deepcopy(INDY_PROOF_REQ_PRED_NAMES), - INDY_PROOF_X, - "schemas", - {"LjgpST2rjsoxYegQDRm7EL:3:CL:18:tag": {"value": {}}}, - REV_REG_DEFS, - "rev_reg_entries", - ) - - mock_verify.assert_not_called() - - assert verified is False - assert len(msgs) == 3 - assert "RMV_RFNT_NRI::18_busid_GE_uuid" in msgs - assert "RMV_RFNT_NRI::18_id_GE_uuid" in msgs - assert "VALUE_ERROR::Missing requested attribute group 18_uuid" in msgs diff --git a/aries_cloudagent/anoncreds/sdk/tests/test_wallet_plugin.py b/aries_cloudagent/anoncreds/sdk/tests/test_wallet_plugin.py deleted file mode 100644 index d683e7b215..0000000000 --- a/aries_cloudagent/anoncreds/sdk/tests/test_wallet_plugin.py +++ /dev/null @@ -1,129 +0,0 @@ -import pytest - -from asynctest import mock as async_mock, TestCase as AsyncTestCase - -from ....ledger.base import BaseLedger -from ....wallet.base import BaseWallet -from ....wallet.did_info import DIDInfo - -from .. import wallet_plugin as test_module - - -class TestWalletCrypto(AsyncTestCase): - def setUp(self): - test_module.LOADED = False - - async def test_file_ext(self): - assert test_module.file_ext() - - def test_load_postgres_plugin(self): - storage_config = '{"wallet_scheme":"MultiWalletSingleTable"}' - storage_creds = '{"account":"test"}' - mock_stg_lib = async_mock.MagicMock( - postgresstorage_init=async_mock.MagicMock(return_value=0), - init_storagetype=async_mock.MagicMock(return_value=0), - ) - with async_mock.patch.object( - test_module.cdll, "LoadLibrary", async_mock.Mock() - ) as mock_load: - mock_load.return_value = mock_stg_lib - test_module.load_postgres_plugin(storage_config, storage_creds) - - assert test_module.LOADED - - def test_load_postgres_plugin_init_x_raise(self): - storage_config = '{"wallet_scheme":"MultiWalletSingleTable"}' - storage_creds = '{"account":"test"}' - mock_stg_lib = async_mock.MagicMock( - postgresstorage_init=async_mock.MagicMock(return_value=2) - ) - with async_mock.patch.object( - test_module.cdll, "LoadLibrary", async_mock.Mock() - ) as mock_load: - mock_load.return_value = mock_stg_lib - with self.assertRaises(OSError) as context: - test_module.load_postgres_plugin( - storage_config, storage_creds, raise_exc=True - ) - assert "unable to load postgres" in str(context.exception) - - def test_load_postgres_plugin_init_x_exit(self): - storage_config = '{"wallet_scheme":"MultiWalletSingleTable"}' - storage_creds = '{"account":"test"}' - mock_stg_lib = async_mock.MagicMock( - postgresstorage_init=async_mock.MagicMock(return_value=2) - ) - with async_mock.patch.object( - test_module.cdll, "LoadLibrary", async_mock.Mock() - ) as mock_load: - mock_load.return_value = mock_stg_lib - with self.assertRaises(SystemExit): - test_module.load_postgres_plugin( - storage_config, storage_creds, raise_exc=False - ) - - def test_load_postgres_plugin_config_x_raise(self): - storage_config = '{"wallet_scheme":"MultiWalletSingleTable"}' - storage_creds = '{"account":"test"}' - mock_stg_lib = async_mock.MagicMock( - postgresstorage_init=async_mock.MagicMock(return_value=0), - init_storagetype=async_mock.MagicMock(return_value=2), - ) - with async_mock.patch.object( - test_module.cdll, "LoadLibrary", async_mock.Mock() - ) as mock_load: - mock_load.return_value = mock_stg_lib - with self.assertRaises(OSError) as context: - test_module.load_postgres_plugin( - storage_config, storage_creds, raise_exc=True - ) - assert "unable to configure postgres" in str(context.exception) - - def test_load_postgres_plugin_config_x_exit(self): - storage_config = '{"wallet_scheme":"MultiWalletSingleTable"}' - storage_creds = '{"account":"test"}' - mock_stg_lib = async_mock.MagicMock( - postgresstorage_init=async_mock.MagicMock(return_value=0), - init_storagetype=async_mock.MagicMock(return_value=2), - ) - with async_mock.patch.object( - test_module.cdll, "LoadLibrary", async_mock.Mock() - ) as mock_load: - mock_load.return_value = mock_stg_lib - with self.assertRaises(SystemExit): - test_module.load_postgres_plugin( - storage_config, storage_creds, raise_exc=False - ) - - def test_load_postgres_plugin_bad_json_x_raise(self): - storage_config = '{"wallet_scheme":"MultiWalletSingleTable"}' - storage_creds = '"account":"test"' - mock_stg_lib = async_mock.MagicMock( - postgresstorage_init=async_mock.MagicMock(return_value=0), - init_storagetype=async_mock.MagicMock(return_value=2), - ) - with async_mock.patch.object( - test_module.cdll, "LoadLibrary", async_mock.Mock() - ) as mock_load: - mock_load.return_value = mock_stg_lib - with self.assertRaises(OSError) as context: - test_module.load_postgres_plugin( - storage_config, storage_creds, raise_exc=True - ) - assert "Invalid stringified JSON input" in str(context.exception) - - def test_load_postgres_plugin_bad_json_x_exit(self): - storage_config = '"wallet_scheme":"MultiWalletSingleTable"' - storage_creds = '{"account":"test"}' - mock_stg_lib = async_mock.MagicMock( - postgresstorage_init=async_mock.MagicMock(return_value=0), - init_storagetype=async_mock.MagicMock(return_value=2), - ) - with async_mock.patch.object( - test_module.cdll, "LoadLibrary", async_mock.Mock() - ) as mock_load: - mock_load.return_value = mock_stg_lib - with self.assertRaises(SystemExit): - test_module.load_postgres_plugin( - storage_config, storage_creds, raise_exc=False - ) diff --git a/aries_cloudagent/anoncreds/sdk/util.py b/aries_cloudagent/anoncreds/sdk/util.py deleted file mode 100644 index 549d06e965..0000000000 --- a/aries_cloudagent/anoncreds/sdk/util.py +++ /dev/null @@ -1,29 +0,0 @@ -"""Indy utilities.""" - -import json - -from pathlib import Path - -import indy.blob_storage - - -async def create_tails_reader(tails_file_path: str) -> int: - """Get a handle for the blob_storage file reader.""" - tails_file_path = Path(tails_file_path) - - if not tails_file_path.exists(): - raise FileNotFoundError("Tails file does not exist.") - - tails_reader_config = json.dumps( - { - "base_dir": str(tails_file_path.parent.absolute()), - "file": str(tails_file_path.name), - } - ) - return await indy.blob_storage.open_reader("default", tails_reader_config) - - -async def create_tails_writer(tails_base_dir: str) -> int: - """Get a handle for the blob_storage file writer.""" - tails_writer_config = json.dumps({"base_dir": tails_base_dir, "uri_pattern": ""}) - return await indy.blob_storage.open_writer("default", tails_writer_config) diff --git a/aries_cloudagent/anoncreds/sdk/verifier.py b/aries_cloudagent/anoncreds/sdk/verifier.py deleted file mode 100644 index 7a3a39d389..0000000000 --- a/aries_cloudagent/anoncreds/sdk/verifier.py +++ /dev/null @@ -1,88 +0,0 @@ -"""Indy SDK verifier implementation.""" - -import json -import logging - -import indy.anoncreds -from indy.error import IndyError - -from ...core.profile import Profile - -from ..verifier import AnonCredsVerifier, PresVerifyMsg - -LOGGER = logging.getLogger(__name__) - - -class IndySdkVerifier(AnonCredsVerifier): - """Indy-SDK verifier implementation.""" - - def __init__(self, profile: Profile): - """ - Initialize an AnonCredsVerifier instance. - - Args: - profile: Active Profile instance - - """ - self.profile = profile - - async def verify_presentation( - self, - pres_req, - pres, - schemas, - credential_definitions, - rev_reg_defs, - rev_reg_entries, - ) -> (bool, list): - """ - Verify a presentation. - - Args: - pres_req: Presentation request data - pres: Presentation data - schemas: Schema data - credential_definitions: credential definition data - rev_reg_defs: revocation registry definitions - rev_reg_entries: revocation registry entries - """ - - LOGGER.debug(f">>> received presentation: {pres}") - LOGGER.debug(f">>> for pres_req: {pres_req}") - msgs = [] - try: - msgs += self.non_revoc_intervals(pres_req, pres, credential_definitions) - msgs += await self.check_timestamps( - self.profile, pres_req, pres, rev_reg_defs - ) - msgs += await self.pre_verify(pres_req, pres) - except ValueError as err: - s = str(err) - msgs.append(f"{PresVerifyMsg.PRES_VALUE_ERROR.value}::{s}") - LOGGER.error( - f"Presentation on nonce={pres_req['nonce']} " - f"cannot be validated: {str(err)}" - ) - return (False, msgs) - - LOGGER.debug(f">>> verifying presentation: {pres}") - LOGGER.debug(f">>> for pres_req: {pres_req}") - try: - verified = await indy.anoncreds.verifier_verify_proof( - json.dumps(pres_req), - json.dumps(pres), - json.dumps(schemas), - json.dumps(credential_definitions), - json.dumps(rev_reg_defs), - json.dumps(rev_reg_entries), - ) - except IndyError as err: - s = str(err) - msgs.append(f"{PresVerifyMsg.PRES_VERIFY_ERROR.value}::{s}") - LOGGER.exception( - f"Validation of presentation on nonce={pres_req['nonce']} " - "failed with error" - ) - verified = False - - return (verified, msgs) diff --git a/aries_cloudagent/anoncreds/sdk/wallet_plugin.py b/aries_cloudagent/anoncreds/sdk/wallet_plugin.py deleted file mode 100644 index 8dbdb24775..0000000000 --- a/aries_cloudagent/anoncreds/sdk/wallet_plugin.py +++ /dev/null @@ -1,63 +0,0 @@ -"""Utility for loading Postgres wallet plug-in.""" - -import logging -import platform -import json -from ctypes import cdll, c_char_p - -EXTENSION = {"darwin": ".dylib", "linux": ".so", "win32": ".dll", "windows": ".dll"} -LOADED = False -LOGGER = logging.getLogger(__name__) - - -def file_ext(): - """Determine file extension based on platform.""" - your_platform = platform.system().lower() - return EXTENSION[your_platform] if (your_platform in EXTENSION) else ".so" - - -def load_postgres_plugin(storage_config, storage_creds, raise_exc=False): - """Load postgres dll and configure postgres wallet.""" - global LOADED, LOGGER - - if not LOADED: - LOGGER.info( - "Checking input postgres storage_config and storage_creds arguments" - ) - try: - json.loads(storage_config) - json.loads(storage_creds) - except json.decoder.JSONDecodeError: - LOGGER.error( - "Invalid stringified JSON input, check storage_config and storage_creds" - ) - if raise_exc: - raise OSError( - "Invalid stringified JSON input, " - "check storage_config and storage_creds" - ) - else: - raise SystemExit(1) - - LOGGER.info("Initializing postgres wallet") - stg_lib = cdll.LoadLibrary("libindystrgpostgres" + file_ext()) - result = stg_lib.postgresstorage_init() - if result != 0: - LOGGER.error("Error unable to load postgres wallet storage: %s", result) - if raise_exc: - raise OSError(f"Error unable to load postgres wallet storage: {result}") - else: - raise SystemExit(1) - if "wallet_scheme" in storage_config: - c_config = c_char_p(storage_config.encode("utf-8")) - c_credentials = c_char_p(storage_creds.encode("utf-8")) - result = stg_lib.init_storagetype(c_config, c_credentials) - if result != 0: - LOGGER.error("Error unable to configure postgres stg: %s", result) - if raise_exc: - raise OSError(f"Error unable to configure postgres stg: {result}") - else: - raise SystemExit(1) - LOADED = True - - LOGGER.info("Success, loaded postgres wallet storage") diff --git a/aries_cloudagent/anoncreds/sdk/wallet_setup.py b/aries_cloudagent/anoncreds/sdk/wallet_setup.py deleted file mode 100644 index 280f7abdc9..0000000000 --- a/aries_cloudagent/anoncreds/sdk/wallet_setup.py +++ /dev/null @@ -1,236 +0,0 @@ -"""Indy-SDK wallet setup and configuration.""" - -import json -import logging - -from typing import Any, Mapping - -import indy.anoncreds -import indy.did -import indy.crypto -import indy.wallet - -from indy.error import IndyError, ErrorCode - -from ...core.error import ProfileError, ProfileDuplicateError, ProfileNotFoundError -from ...core.profile import Profile - -from .error import IndyErrorHandler -from .wallet_plugin import load_postgres_plugin - -LOGGER = logging.getLogger(__name__) - - -class IndyWalletConfig: - """A helper class for handling Indy-SDK wallet configuration.""" - - DEFAULT_FRESHNESS = False - DEFAULT_KEY = "" - DEFAULT_KEY_DERIVATION = "ARGON2I_MOD" - DEFAULT_STORAGE_TYPE = None - - KEY_DERIVATION_RAW = "RAW" - KEY_DERIVATION_ARGON2I_INT = "ARGON2I_INT" - KEY_DERIVATION_ARGON2I_MOD = "ARGON2I_MOD" - - def __init__(self, config: Mapping[str, Any] = None): - """Initialize an `IndySdkWalletConfig` instance. - - Args: - config: {name, key, seed, did, auto_recreate, auto_remove, - storage_type, storage_config, storage_creds} - - """ - - config = config or {} - self.auto_recreate = config.get("auto_recreate", False) - self.auto_remove = config.get("auto_remove", False) - self.freshness_time = config.get("freshness_time", self.DEFAULT_FRESHNESS) - self.key = config.get("key", self.DEFAULT_KEY) - self.key_derivation_method = ( - config.get("key_derivation_method") or self.DEFAULT_KEY_DERIVATION - ) - # self.rekey = config.get("rekey") - # self.rekey_derivation_method = config.get("rekey_derivation_method") - self.name = config.get("name") or Profile.DEFAULT_NAME - self.storage_type = config.get("storage_type") or self.DEFAULT_STORAGE_TYPE - self.storage_config = config.get("storage_config", None) - self.storage_creds = config.get("storage_creds", None) - - if self.storage_type == "postgres_storage": - load_postgres_plugin(self.storage_config, self.storage_creds) - - @property - def wallet_config(self) -> dict: - """Accessor for the Indy wallet config.""" - ret = { - "id": self.name, - "freshness_time": self.freshness_time, - "storage_type": self.storage_type, - } - if self.storage_config is not None: - ret["storage_config"] = json.loads(self.storage_config) - return ret - - @property - def wallet_access(self) -> dict: - """Accessor the Indy wallet access info.""" - ret = {"key": self.key, "key_derivation_method": self.key_derivation_method} - # if self.rekey: - # ret["rekey"] = self.rekey - # if self.rekey_derivation_method: - # ret["rekey_derivation_method"] = self.rekey_derivation_method - if self.storage_creds is not None: - ret["storage_credentials"] = json.loads(self.storage_creds) - return ret - - async def create_wallet(self) -> "IndyOpenWallet": - """ - Create a new wallet. - - Raises: - ProfileDuplicateError: If there was an existing wallet with the same name - ProfileError: If there was a problem removing the wallet - ProfileError: If there was another libindy error - - """ - if self.auto_recreate: - try: - await self.remove_wallet() - except ProfileNotFoundError: - pass - try: - await indy.wallet.create_wallet( - config=json.dumps(self.wallet_config), - credentials=json.dumps(self.wallet_access), - ) - except IndyError as x_indy: - if x_indy.error_code == ErrorCode.WalletAlreadyExistsError: - raise IndyErrorHandler.wrap_error( - x_indy, - f"Cannot create wallet '{self.name}', already exists", - ProfileDuplicateError, - ) from x_indy - raise IndyErrorHandler.wrap_error( - x_indy, - f"Error creating wallet '{self.name}'", - ProfileError, - ) from x_indy - - try: - return await self.open_wallet(created=True) - except ProfileNotFoundError as err: - raise ProfileError( - f"Wallet '{self.name}' not found after creation" - ) from err - - async def remove_wallet(self): - """ - Remove an existing wallet. - - Raises: - ProfileNotFoundError: If the wallet could not be found - ProfileError: If there was another libindy error - - """ - try: - await indy.wallet.delete_wallet( - config=json.dumps(self.wallet_config), - credentials=json.dumps(self.wallet_access), - ) - except IndyError as x_indy: - if x_indy.error_code == ErrorCode.WalletNotFoundError: - raise IndyErrorHandler.wrap_error( - x_indy, - f"Wallet '{self.name}' not found", - ProfileNotFoundError, - ) from x_indy - raise IndyErrorHandler.wrap_error( - x_indy, f"Error removing wallet '{self.name}'", ProfileError - ) from x_indy - - async def open_wallet(self, created: bool = False) -> "IndyOpenWallet": - """ - Open wallet, removing and/or creating it if so configured. - - Raises: - ProfileError: If wallet not found after creation - ProfileNotFoundError: If the wallet is not found - ProfileError: If the wallet is already open - ProfileError: If there is another libindy error - - """ - handle = None - - while True: - try: - handle = await indy.wallet.open_wallet( - config=json.dumps(self.wallet_config), - credentials=json.dumps(self.wallet_access), - ) - # if self.rekey: - # self.key = self.rekey - # self.rekey = None - # if self.rekey_derivation_method: - # self.key_derivation_method = self.rekey_derivation_method - # self.rekey_derivation_method = None - break - except IndyError as x_indy: - if x_indy.error_code == ErrorCode.WalletNotFoundError: - raise IndyErrorHandler.wrap_error( - x_indy, f"Wallet '{self.name}' not found", ProfileNotFoundError - ) from x_indy - elif x_indy.error_code == ErrorCode.WalletAlreadyOpenedError: - raise IndyErrorHandler.wrap_error( - x_indy, f"Wallet '{self.name}' is already open", ProfileError - ) from x_indy - else: - raise IndyErrorHandler.wrap_error( - x_indy, f"Error opening wallet '{self.name}'", ProfileError - ) from x_indy - - LOGGER.info("Creating master secret...") - try: - master_secret_id = await indy.anoncreds.prover_create_master_secret( - handle, self.name - ) - except IndyError as x_indy: - if x_indy.error_code == ErrorCode.AnoncredsMasterSecretDuplicateNameError: - LOGGER.info("Master secret already exists") - master_secret_id = self.name - else: - raise IndyErrorHandler.wrap_error( - x_indy, f"Wallet '{self.name}' error", ProfileError - ) from x_indy - - return IndyOpenWallet(self, created, handle, master_secret_id) - - -class IndyOpenWallet: - """Handle and metadata for an opened Indy wallet.""" - - def __init__( - self, - config: IndyWalletConfig, - created, - handle, - master_secret_id: str, - ): - """Create a new IndyOpenWallet instance.""" - self.config = config - self.created = created - self.handle = handle - self.master_secret_id = master_secret_id - - @property - def name(self) -> str: - """Accessor for the opened wallet name.""" - return self.config.name - - async def close(self): - """Close previously-opened wallet, removing it if so configured.""" - if self.handle: - await indy.wallet.close_wallet(self.handle) - self.handle = None - if self.config.auto_remove: - await self.config.remove_wallet() From 343fa08fc925a878d9f97200a857fcd2977033fa Mon Sep 17 00:00:00 2001 From: Char Howland Date: Thu, 30 Mar 2023 09:39:15 -0600 Subject: [PATCH 090/150] feat: drop registry from default registry folder names Signed-off-by: Char Howland --- .../anoncreds/default/{did_indy_registry => did_indy}/__init__.py | 0 .../anoncreds/default/{did_indy_registry => did_indy}/registry.py | 0 .../anoncreds/default/{did_indy_registry => did_indy}/routes.py | 0 .../anoncreds/default/{did_web_registry => did_web}/__init__.py | 0 .../anoncreds/default/{did_web_registry => did_web}/registry.py | 0 .../anoncreds/default/{did_web_registry => did_web}/routes.py | 0 .../default/{legacy_indy_registry => legacy_indy}/__init__.py | 0 .../default/{legacy_indy_registry => legacy_indy}/registry.py | 0 .../default/{legacy_indy_registry => legacy_indy}/routes.py | 0 9 files changed, 0 insertions(+), 0 deletions(-) rename aries_cloudagent/anoncreds/anoncreds/default/{did_indy_registry => did_indy}/__init__.py (100%) rename aries_cloudagent/anoncreds/anoncreds/default/{did_indy_registry => did_indy}/registry.py (100%) rename aries_cloudagent/anoncreds/anoncreds/default/{did_indy_registry => did_indy}/routes.py (100%) rename aries_cloudagent/anoncreds/anoncreds/default/{did_web_registry => did_web}/__init__.py (100%) rename aries_cloudagent/anoncreds/anoncreds/default/{did_web_registry => did_web}/registry.py (100%) rename aries_cloudagent/anoncreds/anoncreds/default/{did_web_registry => did_web}/routes.py (100%) rename aries_cloudagent/anoncreds/anoncreds/default/{legacy_indy_registry => legacy_indy}/__init__.py (100%) rename aries_cloudagent/anoncreds/anoncreds/default/{legacy_indy_registry => legacy_indy}/registry.py (100%) rename aries_cloudagent/anoncreds/anoncreds/default/{legacy_indy_registry => legacy_indy}/routes.py (100%) diff --git a/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/__init__.py b/aries_cloudagent/anoncreds/anoncreds/default/did_indy/__init__.py similarity index 100% rename from aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/__init__.py rename to aries_cloudagent/anoncreds/anoncreds/default/did_indy/__init__.py diff --git a/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/did_indy/registry.py similarity index 100% rename from aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/registry.py rename to aries_cloudagent/anoncreds/anoncreds/default/did_indy/registry.py diff --git a/aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/routes.py b/aries_cloudagent/anoncreds/anoncreds/default/did_indy/routes.py similarity index 100% rename from aries_cloudagent/anoncreds/anoncreds/default/did_indy_registry/routes.py rename to aries_cloudagent/anoncreds/anoncreds/default/did_indy/routes.py diff --git a/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/__init__.py b/aries_cloudagent/anoncreds/anoncreds/default/did_web/__init__.py similarity index 100% rename from aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/__init__.py rename to aries_cloudagent/anoncreds/anoncreds/default/did_web/__init__.py diff --git a/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/did_web/registry.py similarity index 100% rename from aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/registry.py rename to aries_cloudagent/anoncreds/anoncreds/default/did_web/registry.py diff --git a/aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/routes.py b/aries_cloudagent/anoncreds/anoncreds/default/did_web/routes.py similarity index 100% rename from aries_cloudagent/anoncreds/anoncreds/default/did_web_registry/routes.py rename to aries_cloudagent/anoncreds/anoncreds/default/did_web/routes.py diff --git a/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/__init__.py b/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy/__init__.py similarity index 100% rename from aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/__init__.py rename to aries_cloudagent/anoncreds/anoncreds/default/legacy_indy/__init__.py diff --git a/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy/registry.py similarity index 100% rename from aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/registry.py rename to aries_cloudagent/anoncreds/anoncreds/default/legacy_indy/registry.py diff --git a/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/routes.py b/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy/routes.py similarity index 100% rename from aries_cloudagent/anoncreds/anoncreds/default/legacy_indy_registry/routes.py rename to aries_cloudagent/anoncreds/anoncreds/default/legacy_indy/routes.py From 2dae060f33abcd46d4d26449983efb184026ca6b Mon Sep 17 00:00:00 2001 From: Char Howland Date: Thu, 30 Mar 2023 09:44:37 -0600 Subject: [PATCH 091/150] refactor: rename registry and base Signed-off-by: Char Howland --- aries_cloudagent/anoncreds/anoncreds/__init__.py | 8 ++++---- .../anoncreds/anoncreds/{base_registry.py => base.py} | 0 .../anoncreds/anoncreds/default/did_indy/registry.py | 2 +- .../anoncreds/anoncreds/default/did_web/registry.py | 2 +- .../anoncreds/anoncreds/default/legacy_indy/registry.py | 2 +- aries_cloudagent/anoncreds/anoncreds/issuer.py | 4 ++-- .../anoncreds/{anoncreds_registry.py => registry.py} | 2 +- aries_cloudagent/anoncreds/anoncreds/routes.py | 2 +- aries_cloudagent/anoncreds/anoncreds/verifier.py | 2 +- aries_cloudagent/anoncreds/verifier.py | 2 +- aries_cloudagent/config/default_context.py | 8 ++++---- .../issue_credential/v2_0/formats/indy/handler.py | 2 +- .../protocols/present_proof/indy/pres_exch_handler.py | 2 +- 13 files changed, 19 insertions(+), 19 deletions(-) rename aries_cloudagent/anoncreds/anoncreds/{base_registry.py => base.py} (100%) rename aries_cloudagent/anoncreds/anoncreds/{anoncreds_registry.py => registry.py} (99%) diff --git a/aries_cloudagent/anoncreds/anoncreds/__init__.py b/aries_cloudagent/anoncreds/anoncreds/__init__.py index 36536b98d0..0490ab538f 100644 --- a/aries_cloudagent/anoncreds/anoncreds/__init__.py +++ b/aries_cloudagent/anoncreds/anoncreds/__init__.py @@ -3,7 +3,7 @@ from ...config.injection_context import InjectionContext from ...config.provider import ClassProvider -from ..anoncreds.anoncreds_registry import AnonCredsRegistry +from .registry import AnonCredsRegistry LOGGER = logging.getLogger(__name__) @@ -16,7 +16,7 @@ async def setup(context: InjectionContext): return indy_registry = ClassProvider( - "aries_cloudagent.anoncreds.anoncreds.default.did_indy_registry.registry" + "aries_cloudagent.anoncreds.anoncreds.default.did_indy.registry" ".DIDIndyRegistry", # supported_identifiers=[], # method_name="did:indy", @@ -25,7 +25,7 @@ async def setup(context: InjectionContext): registry.register(indy_registry) web_registry = ClassProvider( - "aries_cloudagent.anoncreds.anoncreds.default.did_web_registry.registry" + "aries_cloudagent.anoncreds.anoncreds.default.did_web.registry" ".DIDWebRegistry", # supported_identifiers=[], # method_name="did:web", @@ -34,7 +34,7 @@ async def setup(context: InjectionContext): registry.register(web_registry) legacy_indy_registry = ClassProvider( - "aries_cloudagent.anoncreds.anoncreds.default.legacy_indy_registry.registry" + "aries_cloudagent.anoncreds.anoncreds.default.legacy_indy.registry" ".LegacyIndyRegistry", # supported_identifiers=[], # method_name="", diff --git a/aries_cloudagent/anoncreds/anoncreds/base_registry.py b/aries_cloudagent/anoncreds/anoncreds/base.py similarity index 100% rename from aries_cloudagent/anoncreds/anoncreds/base_registry.py rename to aries_cloudagent/anoncreds/anoncreds/base.py diff --git a/aries_cloudagent/anoncreds/anoncreds/default/did_indy/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/did_indy/registry.py index 7fad17b0e5..c1cc09ebda 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/did_indy/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/did_indy/registry.py @@ -13,7 +13,7 @@ AnonCredsRegistryGetRevocationRegistryDefinition, ) from ....models.anoncreds_schema import GetSchemaResult -from ...base_registry import BaseAnonCredsRegistrar, BaseAnonCredsResolver +from ...base import BaseAnonCredsRegistrar, BaseAnonCredsResolver LOGGER = logging.getLogger(__name__) diff --git a/aries_cloudagent/anoncreds/anoncreds/default/did_web/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/did_web/registry.py index ef969d0f4a..2fff2c4a06 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/did_web/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/did_web/registry.py @@ -12,7 +12,7 @@ AnonCredsRegistryGetRevocationRegistryDefinition, ) from ....models.anoncreds_schema import GetSchemaResult -from ...base_registry import BaseAnonCredsRegistrar, BaseAnonCredsResolver +from ...base import BaseAnonCredsRegistrar, BaseAnonCredsResolver class DIDWebRegistry(BaseAnonCredsResolver, BaseAnonCredsRegistrar): diff --git a/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy/registry.py index 9433d26379..abc690c201 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy/registry.py @@ -35,7 +35,7 @@ SchemaResult, SchemaState, ) -from ...base_registry import ( +from ...base import ( AnonCredsObjectAlreadyExists, AnonCredsObjectNotFound, AnonCredsRegistrationError, diff --git a/aries_cloudagent/anoncreds/anoncreds/issuer.py b/aries_cloudagent/anoncreds/anoncreds/issuer.py index 9f582600ac..31bb0c9e99 100644 --- a/aries_cloudagent/anoncreds/anoncreds/issuer.py +++ b/aries_cloudagent/anoncreds/anoncreds/issuer.py @@ -21,8 +21,8 @@ ) -from .anoncreds_registry import AnonCredsRegistry -from .base_registry import AnonCredsSchemaAlreadyExists +from .registry import AnonCredsRegistry +from .base import AnonCredsSchemaAlreadyExists from ..models.anoncreds_cred_def import ( CredDef, CredDefResult, diff --git a/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py b/aries_cloudagent/anoncreds/anoncreds/registry.py similarity index 99% rename from aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py rename to aries_cloudagent/anoncreds/anoncreds/registry.py index eb3923e469..53c75b3b10 100644 --- a/aries_cloudagent/anoncreds/anoncreds/anoncreds_registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/registry.py @@ -18,7 +18,7 @@ RevStatusListResult, ) from ..models.anoncreds_schema import AnonCredsSchema, GetSchemaResult, SchemaResult -from .base_registry import ( +from .base import ( AnonCredsRegistrationError, AnonCredsResolutionError, BaseAnonCredsHandler, diff --git a/aries_cloudagent/anoncreds/anoncreds/routes.py b/aries_cloudagent/anoncreds/anoncreds/routes.py index 9eb94bd5d8..e3d67f5b76 100644 --- a/aries_cloudagent/anoncreds/anoncreds/routes.py +++ b/aries_cloudagent/anoncreds/anoncreds/routes.py @@ -11,7 +11,7 @@ ) from marshmallow import fields -from .anoncreds_registry import AnonCredsRegistry +from .registry import AnonCredsRegistry from ..issuer import AnonCredsIssuer from ..models.anoncreds_cred_def import ( CredDefSchema, diff --git a/aries_cloudagent/anoncreds/anoncreds/verifier.py b/aries_cloudagent/anoncreds/anoncreds/verifier.py index ba45c1c5eb..ced095baee 100644 --- a/aries_cloudagent/anoncreds/anoncreds/verifier.py +++ b/aries_cloudagent/anoncreds/anoncreds/verifier.py @@ -6,7 +6,7 @@ from anoncreds import AnoncredsError, Presentation -from aries_cloudagent.anoncreds.anoncreds.anoncreds_registry import AnonCredsRegistry +from aries_cloudagent.anoncreds.anoncreds.registry import AnonCredsRegistry from ...core.profile import Profile diff --git a/aries_cloudagent/anoncreds/verifier.py b/aries_cloudagent/anoncreds/verifier.py index 9ca7df31b6..5268b9784a 100644 --- a/aries_cloudagent/anoncreds/verifier.py +++ b/aries_cloudagent/anoncreds/verifier.py @@ -8,7 +8,7 @@ from ..core.profile import Profile from ..messaging.util import canon, encode -from .anoncreds.anoncreds_registry import AnonCredsRegistry +from .anoncreds.registry import AnonCredsRegistry from .models.anoncreds_cred_def import GetCredDefResult from .models.xform import indy_proof_req2non_revoc_intervals diff --git a/aries_cloudagent/config/default_context.py b/aries_cloudagent/config/default_context.py index 03ba24f45c..a9b6ff29af 100644 --- a/aries_cloudagent/config/default_context.py +++ b/aries_cloudagent/config/default_context.py @@ -1,6 +1,6 @@ """Classes for configuring the default injection context.""" -from ..anoncreds.anoncreds.anoncreds_registry import AnonCredsRegistry +from ..anoncreds.anoncreds.registry import AnonCredsRegistry from ..cache.base import BaseCache from ..cache.in_memory import InMemoryCache from ..core.event_bus import EventBus @@ -131,13 +131,13 @@ async def load_plugins(self, context: InjectionContext): plugin_registry.register_plugin("aries_cloudagent.wallet") plugin_registry.register_plugin("aries_cloudagent.anoncreds.anoncreds") plugin_registry.register_plugin( - "aries_cloudagent.anoncreds.anoncreds.default.did_indy_registry" + "aries_cloudagent.anoncreds.anoncreds.default.did_indy" ) plugin_registry.register_plugin( - "aries_cloudagent.anoncreds.anoncreds.default.did_web_registry" + "aries_cloudagent.anoncreds.anoncreds.default.did_web" ) plugin_registry.register_plugin( - "aries_cloudagent.anoncreds.anoncreds.default.legacy_indy_registry" + "aries_cloudagent.anoncreds.anoncreds.default.legacy_indy" ) if context.settings.get("multitenant.admin_enabled"): diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/handler.py b/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/handler.py index 19abd4602a..26b7b708f6 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/handler.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/handler.py @@ -7,7 +7,7 @@ from marshmallow import RAISE -from ......anoncreds.anoncreds.anoncreds_registry import AnonCredsRegistry +from ......anoncreds.anoncreds.registry import AnonCredsRegistry from ......anoncreds.holder import AnonCredsHolder, AnonCredsHolderError from ......anoncreds.issuer import ( AnonCredsIssuer, diff --git a/aries_cloudagent/protocols/present_proof/indy/pres_exch_handler.py b/aries_cloudagent/protocols/present_proof/indy/pres_exch_handler.py index 928bf4d155..77fd24a21d 100644 --- a/aries_cloudagent/protocols/present_proof/indy/pres_exch_handler.py +++ b/aries_cloudagent/protocols/present_proof/indy/pres_exch_handler.py @@ -4,7 +4,7 @@ import time from typing import Union -from ....anoncreds.anoncreds.anoncreds_registry import AnonCredsRegistry +from ....anoncreds.anoncreds.registry import AnonCredsRegistry from ....anoncreds.holder import AnonCredsHolder, AnonCredsHolderError from ....anoncreds.models.xform import indy_proof_req2non_revoc_intervals from ....core.error import BaseError From b155cdaf188c609348e6dbe479ca90a1870445a4 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Thu, 30 Mar 2023 11:46:23 -0600 Subject: [PATCH 092/150] refactor: remove abstract AnonCreds* classes, rename AnonCredsRs* classes Signed-off-by: Char Howland --- .../anoncreds/default/legacy_indy/registry.py | 2 +- .../anoncreds/anoncreds/holder.py | 26 +- .../anoncreds/anoncreds/issuer.py | 58 +-- .../anoncreds/anoncreds/routes.py | 2 +- .../anoncreds/anoncreds/verifier.py | 362 +++++++++++++++- aries_cloudagent/anoncreds/holder.py | 198 --------- aries_cloudagent/anoncreds/issuer.py | 252 ----------- aries_cloudagent/anoncreds/models/xform.py | 2 +- aries_cloudagent/anoncreds/verifier.py | 405 ------------------ aries_cloudagent/askar/profile.py | 12 +- aries_cloudagent/core/conductor.py | 2 +- aries_cloudagent/holder/routes.py | 2 +- aries_cloudagent/holder/tests/test_routes.py | 2 +- aries_cloudagent/ledger/base.py | 2 +- aries_cloudagent/ledger/tests/test_indy.py | 6 +- .../ledger/tests/test_indy_vdr.py | 2 +- .../credential_definitions/routes.py | 2 +- .../tests/test_routes.py | 2 +- aries_cloudagent/messaging/schemas/routes.py | 2 +- .../messaging/schemas/tests/test_routes.py | 2 +- .../endorse_transaction/v1_0/manager.py | 2 +- .../endorse_transaction/v1_0/routes.py | 2 +- .../v1_0/handlers/credential_issue_handler.py | 2 +- .../v1_0/handlers/credential_offer_handler.py | 2 +- .../handlers/credential_proposal_handler.py | 2 +- .../handlers/credential_request_handler.py | 2 +- .../issue_credential/v1_0/manager.py | 4 +- .../protocols/issue_credential/v1_0/routes.py | 4 +- .../v1_0/tests/test_manager.py | 4 +- .../v2_0/formats/indy/handler.py | 4 +- .../v2_0/formats/indy/tests/test_handler.py | 4 +- .../v2_0/handlers/cred_issue_handler.py | 2 +- .../v2_0/handlers/cred_offer_handler.py | 2 +- .../v2_0/handlers/cred_proposal_handler.py | 2 +- .../v2_0/handlers/cred_request_handler.py | 2 +- .../protocols/issue_credential/v2_0/routes.py | 4 +- .../v2_0/tests/test_manager.py | 2 +- .../present_proof/indy/pres_exch_handler.py | 2 +- .../handlers/presentation_request_handler.py | 2 +- .../test_presentation_request_handler.py | 2 +- .../protocols/present_proof/v1_0/manager.py | 2 +- .../protocols/present_proof/v1_0/routes.py | 2 +- .../present_proof/v1_0/tests/test_manager.py | 8 +- .../present_proof/v1_0/tests/test_routes.py | 8 +- .../v2_0/formats/indy/handler.py | 4 +- .../v2_0/handlers/pres_request_handler.py | 2 +- .../tests/test_pres_request_handler.py | 2 +- .../protocols/present_proof/v2_0/routes.py | 2 +- .../present_proof/v2_0/tests/test_manager.py | 4 +- .../present_proof/v2_0/tests/test_routes.py | 8 +- aries_cloudagent/revocation/manager.py | 2 +- .../models/issuer_rev_reg_record.py | 2 +- .../tests/test_issuer_rev_reg_record.py | 2 +- aries_cloudagent/revocation/routes.py | 2 +- .../revocation/tests/test_manager.py | 2 +- 55 files changed, 475 insertions(+), 972 deletions(-) delete mode 100644 aries_cloudagent/anoncreds/holder.py delete mode 100644 aries_cloudagent/anoncreds/issuer.py delete mode 100644 aries_cloudagent/anoncreds/verifier.py diff --git a/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy/registry.py index abc690c201..b766e9d22f 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy/registry.py @@ -17,7 +17,7 @@ from .....revocation.error import RevocationError from .....revocation.indy import IndyRevocation from .....storage.error import StorageNotFoundError -from ....issuer import AnonCredsIssuer, AnonCredsIssuerError +from ...issuer import AnonCredsIssuer, AnonCredsIssuerError from ....models.anoncreds_cred_def import ( CredDef, CredDefState, diff --git a/aries_cloudagent/anoncreds/anoncreds/holder.py b/aries_cloudagent/anoncreds/anoncreds/holder.py index e1ffc1b246..3e3cace0dc 100644 --- a/aries_cloudagent/anoncreds/anoncreds/holder.py +++ b/aries_cloudagent/anoncreds/anoncreds/holder.py @@ -18,14 +18,12 @@ Presentation, PresentCredentials, ) +from ...core.error import BaseError from ..models.anoncreds_cred_def import CredDef -from ...askar.profile import AskarProfile -from ...ledger.base import BaseLedger from ...wallet.error import WalletNotFoundError -from ..holder import AnonCredsHolder, AnonCredsHolderError LOGGER = logging.getLogger(__name__) @@ -50,14 +48,19 @@ def _normalize_attr_name(name: str) -> str: return name.replace(" ", "") -class AnonCredsRsHolder(AnonCredsHolder): +class AnonCredsHolderError(BaseError): + """Base class for holder exceptions.""" + + +class AnonCredsHolder: """AnonCreds holder class.""" MASTER_SECRET_ID = "default" - def __init__(self, profile: AskarProfile): + def __init__(self, profile): + # TODO: fix circular dependency issue with AskarProfile preventing type hinting """ - Initialize an AnonCredsRsHolder instance. + Initialize an AnonCredsHolder instance. Args: profile: The active profile instance @@ -66,7 +69,7 @@ def __init__(self, profile: AskarProfile): self._profile = profile @property - def profile(self) -> AskarProfile: + def profile(self): """Accessor for the profile instance.""" return self._profile @@ -77,7 +80,7 @@ async def get_master_secret(self) -> MasterSecret: async with self._profile.session() as session: try: record = await session.handle.fetch( - CATEGORY_MASTER_SECRET, AnonCredsRsHolder.MASTER_SECRET_ID + CATEGORY_MASTER_SECRET, AnonCredsHolder.MASTER_SECRET_ID ) except AskarError as err: raise AnonCredsHolderError("Error fetching master secret") from err @@ -99,7 +102,7 @@ async def get_master_secret(self) -> MasterSecret: try: await session.handle.insert( CATEGORY_MASTER_SECRET, - AnonCredsRsHolder.MASTER_SECRET_ID, + AnonCredsHolder.MASTER_SECRET_ID, secret.to_json_buffer(), ) except AskarError as err: @@ -139,7 +142,7 @@ async def create_credential_request( holder_did, credential_definition.to_native(), secret, - AnonCredsRsHolder.MASTER_SECRET_ID, + AnonCredsHolder.MASTER_SECRET_ID, credential_offer, ) except AnoncredsError as err: @@ -395,8 +398,9 @@ async def _get_credential(self, credential_id: str) -> Credential: raise AnonCredsHolderError("Error loading requested credential") from err async def credential_revoked( - self, ledger: BaseLedger, credential_id: str, fro: int = None, to: int = None + self, ledger, credential_id: str, fro: int = None, to: int = None ) -> bool: + # TODO: fix circular dependency issue with BaseLedger preventing type hinting """ Check ledger for revocation status of credential by cred id. diff --git a/aries_cloudagent/anoncreds/anoncreds/issuer.py b/aries_cloudagent/anoncreds/anoncreds/issuer.py index 31bb0c9e99..de5acf6542 100644 --- a/aries_cloudagent/anoncreds/anoncreds/issuer.py +++ b/aries_cloudagent/anoncreds/anoncreds/issuer.py @@ -3,31 +3,23 @@ import asyncio import logging from time import time - from typing import Optional, Sequence, Tuple -from aries_askar import AskarError - from anoncreds import ( - Schema, + AnoncredsError, Credential, CredentialDefinition, CredentialOffer, CredentialRevocationConfig, - AnoncredsError, RevocationRegistryDefinition, RevocationRegistryDelta, RevocationStatusList, + Schema, ) +from aries_askar import AskarError - -from .registry import AnonCredsRegistry -from .base import AnonCredsSchemaAlreadyExists -from ..models.anoncreds_cred_def import ( - CredDef, - CredDefResult, - CredDefState, -) +from ...core.error import BaseError +from ..models.anoncreds_cred_def import CredDef, CredDefResult, CredDefState from ..models.anoncreds_revocation import ( RevRegDef, RevRegDefResult, @@ -35,19 +27,13 @@ RevStatusList, ) from ..models.anoncreds_schema import AnonCredsSchema, SchemaResult, SchemaState - -from ...askar.profile import AskarProfile - -from ..issuer import ( - AnonCredsIssuer, - AnonCredsIssuerError, - AnonCredsIssuerRevocationRegistryFullError, - DEFAULT_CRED_DEF_TAG, - DEFAULT_SIGNATURE_TYPE, -) +from .base import AnonCredsSchemaAlreadyExists +from .registry import AnonCredsRegistry LOGGER = logging.getLogger(__name__) +DEFAULT_CRED_DEF_TAG = "default" +DEFAULT_SIGNATURE_TYPE = "CL" CATEGORY_CRED_DEF = "credential_def" CATEGORY_CRED_DEF_PRIVATE = "credential_def_private" CATEGORY_CRED_DEF_KEY_PROOF = "credential_def_key_proof" @@ -59,10 +45,19 @@ CATEGORY_REV_REG_ISSUER = "revocation_reg_def_issuer" -class AnonCredsRsIssuer(AnonCredsIssuer): - """Indy-Credx issuer class.""" +class AnonCredsIssuerError(BaseError): + """Generic issuer error.""" + - def __init__(self, profile: AskarProfile): +class AnonCredsIssuerRevocationRegistryFullError(AnonCredsIssuerError): + """Revocation registry is full when issuing a new credential.""" + + +class AnonCredsIssuer: + """AnonCreds issuer class.""" + + def __init__(self, profile): + # TODO: fix circular dependency issue with AskarProfile preventing type hinting """ Initialize an IndyCredxIssuer instance. @@ -73,7 +68,7 @@ def __init__(self, profile: AskarProfile): self._profile = profile @property - def profile(self) -> AskarProfile: + def profile(self): """Accessor for the profile instance.""" return self._profile @@ -229,6 +224,15 @@ async def get_created_schemas( # entry.name was stored as the schema's ID return [entry.name for entry in schemas] + @staticmethod + def make_credential_definition_id( + origin_did: str, schema: dict, signature_type: str = None, tag: str = None + ) -> str: + """Derive the ID for a credential definition.""" + signature_type = signature_type or DEFAULT_SIGNATURE_TYPE + tag = tag or DEFAULT_CRED_DEF_TAG + return f"{origin_did}:3:{signature_type}:{str(schema['seqNo'])}:{tag}" + async def credential_definition_in_wallet( self, credential_definition_id: str ) -> bool: diff --git a/aries_cloudagent/anoncreds/anoncreds/routes.py b/aries_cloudagent/anoncreds/anoncreds/routes.py index e3d67f5b76..6877b77c8a 100644 --- a/aries_cloudagent/anoncreds/anoncreds/routes.py +++ b/aries_cloudagent/anoncreds/anoncreds/routes.py @@ -12,7 +12,7 @@ from marshmallow import fields from .registry import AnonCredsRegistry -from ..issuer import AnonCredsIssuer +from .issuer import AnonCredsIssuer from ..models.anoncreds_cred_def import ( CredDefSchema, GetCredDefResultSchema, diff --git a/aries_cloudagent/anoncreds/anoncreds/verifier.py b/aries_cloudagent/anoncreds/anoncreds/verifier.py index ced095baee..019c29196f 100644 --- a/aries_cloudagent/anoncreds/anoncreds/verifier.py +++ b/aries_cloudagent/anoncreds/anoncreds/verifier.py @@ -2,25 +2,38 @@ import asyncio import logging -from typing import Tuple +from enum import Enum +from time import time +from typing import List, Mapping, Tuple from anoncreds import AnoncredsError, Presentation -from aries_cloudagent.anoncreds.anoncreds.registry import AnonCredsRegistry - +from ...anoncreds.anoncreds.registry import AnonCredsRegistry +from ...anoncreds.models.anoncreds_cred_def import GetCredDefResult +from ...anoncreds.models.xform import indy_proof_req2non_revoc_intervals from ...core.profile import Profile - -from ..verifier import AnonCredsVerifier, PresVerifyMsg +from ...messaging.util import canon, encode LOGGER = logging.getLogger(__name__) -class AnonCredsRsVerifier(AnonCredsVerifier): +class PresVerifyMsg(str, Enum): + """Credential verification codes.""" + + RMV_REFERENT_NON_REVOC_INTERVAL = "RMV_RFNT_NRI" + RMV_GLOBAL_NON_REVOC_INTERVAL = "RMV_GLB_NRI" + TSTMP_OUT_NON_REVOC_INTRVAL = "TS_OUT_NRI" + CT_UNREVEALED_ATTRIBUTES = "UNRVL_ATTR" + PRES_VALUE_ERROR = "VALUE_ERROR" + PRES_VERIFY_ERROR = "VERIFY_ERROR" + + +class AnonCredsVerifier: """Verifier class.""" def __init__(self, profile: Profile): """ - Initialize an AnonCredsRsVerifier instance. + Initialize an AnonCredsVerifier instance. Args: profile: an active profile instance @@ -28,6 +41,341 @@ def __init__(self, profile: Profile): """ self.profile = profile + def non_revoc_intervals(self, pres_req: dict, pres: dict, cred_defs: dict) -> list: + """ + 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 + with non-revocable credentials in proof: seek and remove. + + Args: + pres_req: presentation request + pres: corresponding presentation + + """ + msgs = [] + for req_proof_key, pres_key in { + "revealed_attrs": "requested_attributes", + "revealed_attr_groups": "requested_attributes", + "predicates": "requested_predicates", + }.items(): + for uuid, spec in pres["requested_proof"].get(req_proof_key, {}).items(): + if ( + "revocation" + not in cred_defs[ + pres["identifiers"][spec["sub_proof_index"]]["cred_def_id"] + ]["value"] + ): + if uuid in pres_req[pres_key] and pres_req[pres_key][uuid].pop( + "non_revoked", None + ): + msgs.append( + f"{PresVerifyMsg.RMV_REFERENT_NON_REVOC_INTERVAL.value}::" + f"{uuid}" + ) + LOGGER.info( + ( + "Amended presentation request (nonce=%s): removed " + "non-revocation interval at %s referent " + "%s; corresponding credential in proof is irrevocable" + ), + pres_req["nonce"], + pres_key, + uuid, + ) + + if all( + ( + spec.get("timestamp") is None + and "revocation" not in cred_defs[spec["cred_def_id"]]["value"] + ) + for spec in pres["identifiers"] + ): + pres_req.pop("non_revoked", None) + msgs.append(PresVerifyMsg.RMV_GLOBAL_NON_REVOC_INTERVAL.value) + LOGGER.warning( + ( + "Amended presentation request (nonce=%s); removed global " + "non-revocation interval; no revocable credentials in proof" + ), + pres_req["nonce"], + ) + return msgs + + async def check_timestamps( + self, + profile: Profile, + pres_req: Mapping, + pres: Mapping, + rev_reg_defs: Mapping, + ) -> list: + """ + Check for suspicious, missing, and superfluous timestamps. + + Raises ValueError on timestamp in the future, prior to rev reg creation, + superfluous or missing. + + Args: + profile: relevant profile + pres_req: indy proof request + pres: indy 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) + LOGGER.debug(f">>> got non-revoc intervals: {non_revoc_intervals}") + + # timestamp for irrevocable credential + cred_defs: List[GetCredDefResult] = [] + for index, ident in enumerate(pres["identifiers"]): + LOGGER.debug(f">>> got (index, ident): ({index},{ident})") + cred_def_id = ident["cred_def_id"] + anoncreds_registry = profile.inject(AnonCredsRegistry) + cred_def_result = await anoncreds_registry.get_credential_definition( + profile, cred_def_id + ) + cred_defs.append(cred_def_result) + if ident.get("timestamp"): + if not cred_def_result.credential_definition.value.revocation: + raise ValueError( + f"Timestamp in presentation identifier #{index} " + f"for irrevocable cred def id {cred_def_id}" + ) + + # timestamp in the future too far in the past + for ident in pres["identifiers"]: + timestamp = ident.get("timestamp") + rev_reg_id = ident.get("rev_reg_id") + + if not timestamp: + continue + + if timestamp > now + 300: # allow 5 min for clock skew + raise ValueError(f"Timestamp {timestamp} is in the future") + reg_def = rev_reg_defs.get(rev_reg_id) + if not reg_def: + raise ValueError(f"Missing registry definition for '{rev_reg_id}'") + if "txnTime" not in reg_def: + raise ValueError( + f"Missing txnTime for registry definition '{rev_reg_id}'" + ) + if timestamp < reg_def["txnTime"]: + raise ValueError( + f"Timestamp {timestamp} predates rev reg {rev_reg_id} creation" + ) + + # timestamp superfluous, missing, or outside non-revocation interval + revealed_attrs = pres["requested_proof"].get("revealed_attrs", {}) + unrevealed_attrs = pres["requested_proof"].get("unrevealed_attrs", {}) + revealed_groups = pres["requested_proof"].get("revealed_attr_groups", {}) + self_attested = pres["requested_proof"].get("self_attested_attrs", {}) + preds = pres["requested_proof"].get("predicates", {}) + for uuid, req_attr in pres_req["requested_attributes"].items(): + if "name" in req_attr: + if uuid in revealed_attrs: + index = revealed_attrs[uuid]["sub_proof_index"] + if cred_defs[index].credential_definition.value.revocation: + timestamp = pres["identifiers"][index].get("timestamp") + if (timestamp is not None) ^ bool( + non_revoc_intervals.get(uuid) + ): + LOGGER.debug(f">>> uuid: {uuid}") + LOGGER.debug( + f">>> revealed_attrs[uuid]: {revealed_attrs[uuid]}" + ) + raise ValueError( + f"Timestamp on sub-proof #{index} " + f"is {'superfluous' if timestamp else 'missing'} " + f"vs. requested attribute {uuid}" + ) + if non_revoc_intervals.get(uuid) and not ( + non_revoc_intervals[uuid].get("from", 0) + < timestamp + < non_revoc_intervals[uuid].get("to", now) + ): + msgs.append( + f"{PresVerifyMsg.TSTMP_OUT_NON_REVOC_INTRVAL.value}::" + f"{uuid}" + ) + LOGGER.info( + f"Timestamp {timestamp} from ledger for item" + f"{uuid} falls outside non-revocation interval " + f"{non_revoc_intervals[uuid]}" + ) + elif uuid in unrevealed_attrs: + # nothing to do, attribute value is not revealed + msgs.append( + f"{PresVerifyMsg.CT_UNREVEALED_ATTRIBUTES.value}::" f"{uuid}" + ) + elif uuid not in self_attested: + raise ValueError( + f"Presentation attributes mismatch requested attribute {uuid}" + ) + + elif "names" in req_attr: + group_spec = revealed_groups.get(uuid) + if ( + group_spec is None + or "sub_proof_index" not in group_spec + or "values" not in group_spec + ): + raise ValueError(f"Missing requested attribute group {uuid}") + index = group_spec["sub_proof_index"] + if cred_defs[index].credential_definition.value.revocation: + timestamp = pres["identifiers"][index].get("timestamp") + if (timestamp is not None) ^ bool(non_revoc_intervals.get(uuid)): + raise ValueError( + f"Timestamp on sub-proof #{index} " + f"is {'superfluous' if timestamp else 'missing'} " + f"vs. requested attribute group {uuid}" + ) + if non_revoc_intervals.get(uuid) and not ( + non_revoc_intervals[uuid].get("from", 0) + < timestamp + < non_revoc_intervals[uuid].get("to", now) + ): + msgs.append( + f"{PresVerifyMsg.TSTMP_OUT_NON_REVOC_INTRVAL.value}::" + f"{uuid}" + ) + LOGGER.warning( + f"Timestamp {timestamp} from ledger for item" + f"{uuid} falls outside non-revocation interval " + f"{non_revoc_intervals[uuid]}" + ) + + for uuid, req_pred in pres_req["requested_predicates"].items(): + pred_spec = preds.get(uuid) + if pred_spec is None or "sub_proof_index" not in pred_spec: + raise ValueError( + f"Presentation predicates mismatch requested predicate {uuid}" + ) + index = pred_spec["sub_proof_index"] + if cred_defs[index].credential_definition.value.revocation: + timestamp = pres["identifiers"][index].get("timestamp") + if (timestamp is not None) ^ bool(non_revoc_intervals.get(uuid)): + raise ValueError( + f"Timestamp on sub-proof #{index} " + f"is {'superfluous' if timestamp else 'missing'} " + f"vs. requested predicate {uuid}" + ) + if non_revoc_intervals.get(uuid) and not ( + non_revoc_intervals[uuid].get("from", 0) + < timestamp + < non_revoc_intervals[uuid].get("to", now) + ): + msgs.append( + f"{PresVerifyMsg.TSTMP_OUT_NON_REVOC_INTRVAL.value}::" f"{uuid}" + ) + LOGGER.warning( + f"Best-effort timestamp {timestamp} " + "from ledger falls outside non-revocation interval " + f"{non_revoc_intervals[uuid]}" + ) + return msgs + + async def pre_verify(self, pres_req: dict, pres: dict) -> list: + """ + Check for essential components and tampering in presentation. + + Visit encoded attribute values against raw, and predicate bounds, + in presentation, cross-reference against presentation request. + + Args: + pres_req: presentation request + pres: corresponding presentation + + """ + msgs = [] + if not ( + pres_req + and "requested_predicates" in pres_req + and "requested_attributes" in pres_req + ): + raise ValueError("Incomplete or missing proof request") + if not pres: + raise ValueError("No proof provided") + if "requested_proof" not in pres: + raise ValueError("Presentation missing 'requested_proof'") + if "proof" not in pres: + raise ValueError("Presentation missing 'proof'") + + for uuid, req_pred in pres_req["requested_predicates"].items(): + try: + canon_attr = canon(req_pred["name"]) + matched = False + found = False + for ge_proof in pres["proof"]["proofs"][ + pres["requested_proof"]["predicates"][uuid]["sub_proof_index"] + ]["primary_proof"]["ge_proofs"]: + pred = ge_proof["predicate"] + if pred["attr_name"] == canon_attr: + found = True + if pred["value"] == req_pred["p_value"]: + matched = True + break + if not matched: + raise ValueError(f"Predicate value != p_value: {pred['attr_name']}") + break + elif not found: + raise ValueError(f"Missing requested predicate '{uuid}'") + except (KeyError, TypeError): + raise ValueError(f"Missing requested predicate '{uuid}'") + + revealed_attrs = pres["requested_proof"].get("revealed_attrs", {}) + unrevealed_attrs = pres["requested_proof"].get("unrevealed_attrs", {}) + revealed_groups = pres["requested_proof"].get("revealed_attr_groups", {}) + self_attested = pres["requested_proof"].get("self_attested_attrs", {}) + for uuid, req_attr in pres_req["requested_attributes"].items(): + if "name" in req_attr: + if uuid in revealed_attrs: + pres_req_attr_spec = {req_attr["name"]: revealed_attrs[uuid]} + elif uuid in unrevealed_attrs: + # unrevealed attribute, nothing to do + pres_req_attr_spec = {} + msgs.append( + f"{PresVerifyMsg.CT_UNREVEALED_ATTRIBUTES.value}::" f"{uuid}" + ) + elif uuid in self_attested: + if not req_attr.get("restrictions"): + continue + raise ValueError( + "Attribute with restrictions cannot be self-attested: " + f"'{req_attr['name']}'" + ) + else: + raise ValueError( + f"Missing requested attribute '{req_attr['name']}'" + ) + elif "names" in req_attr: + group_spec = revealed_groups[uuid] + pres_req_attr_spec = { + attr: { + "sub_proof_index": group_spec["sub_proof_index"], + **group_spec["values"].get(attr), + } + for attr in req_attr["names"] + } + else: + raise ValueError( + f"Request attribute missing 'name' and 'names': '{uuid}'" + ) + + for attr, spec in pres_req_attr_spec.items(): + try: + primary_enco = pres["proof"]["proofs"][spec["sub_proof_index"]][ + "primary_proof" + ]["eq_proof"]["revealed_attrs"][canon(attr)] + except (KeyError, TypeError): + raise ValueError(f"Missing revealed attribute: '{attr}'") + if primary_enco != spec["encoded"]: + raise ValueError(f"Encoded representation mismatch for '{attr}'") + if primary_enco != encode(spec["raw"]): + raise ValueError(f"Encoded representation mismatch for '{attr}'") + return msgs + async def process_pres_identifiers( self, identifiers: list, diff --git a/aries_cloudagent/anoncreds/holder.py b/aries_cloudagent/anoncreds/holder.py deleted file mode 100644 index b7aa912737..0000000000 --- a/aries_cloudagent/anoncreds/holder.py +++ /dev/null @@ -1,198 +0,0 @@ -"""Base Indy Holder class.""" - -from abc import ABC, ABCMeta, abstractmethod -from typing import Optional, Sequence, Tuple, Union - -from .models.anoncreds_cred_def import CredDef - -from ..core.error import BaseError -from ..ledger.base import BaseLedger - - -class AnonCredsHolderError(BaseError): - """Base class for holder exceptions.""" - - -class AnonCredsHolder(ABC, metaclass=ABCMeta): - """Base class for holder.""" - - RECORD_TYPE_MIME_TYPES = "attribute-mime-types" - CHUNK = 256 - - def __repr__(self) -> str: - """ - Return a human readable representation of this class. - - Returns: - A human readable string for this class - - """ - return "<{}>".format(self.__class__.__name__) - - @abstractmethod - async def get_credential(self, credential_id: str) -> str: - """ - Get a credential stored in the wallet. - - Args: - credential_id: Credential id to retrieve - - """ - - @abstractmethod - async def credential_revoked( - self, ledger: BaseLedger, credential_id: str, fro: int = None, to: int = None - ) -> bool: - """ - Check ledger for revocation status of credential by cred id. - - Args: - credential_id: Credential id to check - - """ - - @abstractmethod - async def delete_credential(self, credential_id: str): - """ - Remove a credential stored in the wallet. - - Args: - credential_id: Credential id to remove - - """ - - @abstractmethod - async def get_mime_type( - self, credential_id: str, attr: str = None - ) -> Union[dict, str]: - """ - Get MIME type per attribute (or for all attributes). - - Args: - credential_id: credential id - attr: attribute of interest or omit for all - - Returns: Attribute MIME type or dict mapping attribute names to MIME types - attr_meta_json = all_meta.tags.get(attr) - - """ - - @abstractmethod - async def get_credentials(self, start: int, count: int, wql: dict): - """ - Get credentials stored in the wallet. - - Args: - start: Starting index - count: Number of records to return - wql: wql query dict - - """ - - @abstractmethod - async def get_credentials_for_presentation_request_by_referent( - self, - presentation_request: dict, - referents: Sequence[str], - start: int, - count: int, - extra_query: Optional[dict] = None, - ): - """ - Get credentials stored in the wallet. - - Args: - presentation_request: Valid presentation request from issuer - referents: Presentation request referents to use to search for creds - start: Starting index - count: Maximum number of records to return - extra_query: wql query dict - - """ - - @abstractmethod - async def create_presentation( - self, - presentation_request: dict, - requested_credentials: dict, - schemas: dict, - credential_definitions: dict, - rev_states: dict = None, - ) -> str: - """ - Get credentials stored in the wallet. - - Args: - presentation_request: Valid indy format presentation request - requested_credentials: Indy format requested credentials - schemas: Indy formatted schemas JSON - credential_definitions: Indy formatted credential definitions JSON - rev_states: Indy format revocation states JSON - """ - - @abstractmethod - async def create_credential_request( - self, credential_offer: dict, credential_definition: CredDef, holder_did: str - ) -> Tuple[str, str]: - """ - Create a credential request for the given credential offer. - - Args: - credential_offer: The credential offer to create request for - credential_definition: The credential definition to create an offer for - holder_did: the DID of the agent making the request - - Returns: - A tuple of the credential request and credential request metadata - - """ - - @abstractmethod - async def store_credential( - self, - credential_definition: dict, - credential_data: dict, - credential_request_metadata: dict, - credential_attr_mime_types=None, - credential_id: str = None, - rev_reg_def: dict = None, - ): - """ - Store a credential in the wallet. - - Args: - credential_definition: Credential definition for this credential - credential_data: Credential data generated by the issuer - credential_request_metadata: credential request metadata generated - by the issuer - credential_attr_mime_types: dict mapping attribute names to (optional) - MIME types to store as non-secret record, if specified - credential_id: optionally override the stored credential id - rev_reg_def: revocation registry definition in json - - Returns: - the ID of the stored credential - - """ - - @abstractmethod - async def create_revocation_state( - self, - cred_rev_id: str, - rev_reg_def: dict, - rev_status_list: dict, - tails_file_path: str, - ) -> str: - """ - Create current revocation state for a received credential. - - Args: - cred_rev_id: credential revocation id in revocation registry - rev_reg_def: revocation registry definition - rev_reg_delta: revocation delta - timestamp: delta timestamp - - Returns: - the revocation state - - """ diff --git a/aries_cloudagent/anoncreds/issuer.py b/aries_cloudagent/anoncreds/issuer.py deleted file mode 100644 index 1015793712..0000000000 --- a/aries_cloudagent/anoncreds/issuer.py +++ /dev/null @@ -1,252 +0,0 @@ -"""Base Indy Issuer class.""" - -from abc import ABC, ABCMeta, abstractmethod -from typing import Optional, Sequence, Tuple - -from aries_cloudagent.anoncreds.models.anoncreds_revocation import RevRegDefResult - -from ..core.error import BaseError -from .models.anoncreds_cred_def import CredDefResult -from .models.anoncreds_schema import SchemaResult - - -DEFAULT_CRED_DEF_TAG = "default" -DEFAULT_SIGNATURE_TYPE = "CL" - - -class AnonCredsIssuerError(BaseError): - """Generic issuer error.""" - - -class AnonCredsIssuerRevocationRegistryFullError(AnonCredsIssuerError): - """Revocation registry is full when issuing a new credential.""" - - -class AnonCredsIssuer(ABC, metaclass=ABCMeta): - """Base class for Indy Issuer.""" - - def __repr__(self) -> str: - """ - Return a human readable representation of this class. - - Returns: - A human readable string for this class - - """ - return "<{}>".format(self.__class__.__name__) - - @staticmethod - def make_schema_id(origin_did: str, schema_name: str, schema_version: str) -> str: - """Derive the ID for a schema.""" - return f"{origin_did}:2:{schema_name}:{schema_version}" - - @abstractmethod - async def create_and_register_schema( - self, - issuer_id: str, - name: str, - version: str, - attr_names: Sequence[str], - options: Optional[dict] = None, - ) -> SchemaResult: - """ - Create a new credential schema and store it in the wallet. - - Args: - origin_did: the DID issuing the credential definition - schema_name: the schema name - schema_version: the schema version - attribute_names: a sequence of schema attribute names - - Returns: - A tuple of the schema ID and JSON - - """ - - @abstractmethod - async def finish_schema(self, schema_id: str): - """Mark a schema as finished in the wallet.""" - - @abstractmethod - async def get_created_schemas( - self, - name: Optional[str] = None, - version: Optional[str] = None, - issuer_id: Optional[str] = None, - ) -> Sequence[str]: - """Retrieve stored schemas from the wallet.""" - - @staticmethod - def make_credential_definition_id( - origin_did: str, schema: dict, signature_type: str = None, tag: str = None - ) -> str: - """Derive the ID for a credential definition.""" - signature_type = signature_type or DEFAULT_SIGNATURE_TYPE - tag = tag or DEFAULT_CRED_DEF_TAG - return f"{origin_did}:3:{signature_type}:{str(schema['seqNo'])}:{tag}" - - @abstractmethod - async def credential_definition_in_wallet( - self, credential_definition_id: str - ) -> bool: - """ - Check whether a given credential definition ID is present in the wallet. - - Args: - credential_definition_id: The credential definition ID to check - """ - - @abstractmethod - async def create_and_register_credential_definition( - self, - issuer_id: str, - schema_id: str, - tag: Optional[str] = None, - signature_type: Optional[str] = None, - options: Optional[dict] = None, - ) -> CredDefResult: - """ - Create a new credential definition and store it in the wallet. - - Args: - origin_did: the DID issuing the credential definition - schema_json: the schema used as a basis - signature_type: the credential definition signature type (default 'CL') - tag: the credential definition tag - support_revocation: whether to enable revocation for this credential def - - Returns: - A tuple of the credential definition ID and JSON - - """ - - @abstractmethod - async def get_created_credential_definitions( - self, - issuer_id: Optional[str] = None, - schema_id: Optional[str] = None, - schema_name: Optional[str] = None, - schema_version: Optional[str] = None, - state: Optional[str] = None, - ) -> Sequence[str]: - """Get created credential definitions.""" - - @abstractmethod - async def match_created_credential_definitions( - self, - cred_def_id: Optional[str] = None, - issuer_id: Optional[str] = None, - schema_issuer_id: Optional[str] = None, - schema_id: Optional[str] = None, - schema_name: Optional[str] = None, - schema_version: Optional[str] = None, - state: Optional[str] = None, - epoch: Optional[str] = None, - ) -> Optional[str]: - """Return cred def id of most recent matching cred def.""" - - @abstractmethod - async def cred_def_supports_revocation(self, cred_def_id: str) -> bool: - """Return whether a credential definition supports revocation.""" - - @abstractmethod - async def create_credential_offer(self, credential_definition_id) -> str: - """ - Create a credential offer for the given credential definition id. - - Args: - credential_definition_id: The credential definition to create an offer for - - Returns: - The created credential offer - - """ - - @abstractmethod - async def create_credential( - self, - schema: dict, - credential_offer: dict, - credential_request: dict, - credential_values: dict, - revoc_reg_id: str = None, - tails_file_path: str = None, - ) -> Tuple[str, str]: - """ - Create a credential. - - Args - schema: Schema to create credential for - credential_offer: Credential Offer to create credential for - credential_request: Credential request to create credential for - credential_values: Values to go in credential - revoc_reg_id: ID of the revocation registry - tails_file_path: The location of the tails file - - Returns: - A tuple of created credential and revocation id - - """ - - @abstractmethod - async def revoke_credentials( - self, - revoc_reg_id: str, - tails_file_path: str, - cred_rev_ids: Sequence[str], - ) -> Tuple[str, Sequence[str]]: - """ - Revoke a set of credentials in a revocation registry. - - Args: - revoc_reg_id: ID of the revocation registry - tails_file_path: path to the local tails file - cred_rev_ids: sequences of credential indexes in the revocation registry - - Returns: - Tuple with the combined revocation delta, list of cred rev ids not revoked - - """ - - @abstractmethod - async def create_and_register_revocation_registry_definition( - self, - issuer_id: str, - cred_def_id: str, - registry_type: str, - tag: str, - max_cred_num: int, - tails_base_path: str, - options: Optional[dict] = None, - ) -> RevRegDefResult: - """ - Create a new revocation registry and store it in the wallet. - - Args: - origin_did: the DID issuing the revocation registry - cred_def_id: the identifier of the related credential definition - revoc_def_type: the revocation registry type (default CL_ACCUM) - tag: the unique revocation registry tag - max_cred_num: the number of credentials supported in the registry - tails_base_path: where to store the tails file - - Returns: - A tuple of the revocation registry ID, JSON, and entry JSON - - """ - - @abstractmethod - async def merge_revocation_registry_deltas( - self, fro_delta: str, to_delta: str - ) -> str: - """ - Merge revocation registry deltas. - - Args: - fro_delta: original delta in JSON format - to_delta: incoming delta in JSON format - - Returns: - Merged delta in JSON format - - """ diff --git a/aries_cloudagent/anoncreds/models/xform.py b/aries_cloudagent/anoncreds/models/xform.py index 8dd6ac0fd9..53c0044dea 100644 --- a/aries_cloudagent/anoncreds/models/xform.py +++ b/aries_cloudagent/anoncreds/models/xform.py @@ -1,6 +1,6 @@ """Utilities to deal with indy.""" -from ...anoncreds.holder import AnonCredsHolder +from ...anoncreds.anoncreds.holder import AnonCredsHolder from .pres_preview import IndyPresPreview diff --git a/aries_cloudagent/anoncreds/verifier.py b/aries_cloudagent/anoncreds/verifier.py deleted file mode 100644 index 5268b9784a..0000000000 --- a/aries_cloudagent/anoncreds/verifier.py +++ /dev/null @@ -1,405 +0,0 @@ -"""Base Indy Verifier class.""" - -from abc import ABC, ABCMeta, abstractmethod -from enum import Enum -import logging -from time import time -from typing import List, Mapping, Tuple - -from ..core.profile import Profile -from ..messaging.util import canon, encode -from .anoncreds.registry import AnonCredsRegistry -from .models.anoncreds_cred_def import GetCredDefResult -from .models.xform import indy_proof_req2non_revoc_intervals - - -LOGGER = logging.getLogger(__name__) - - -class PresVerifyMsg(str, Enum): - """Credential verification codes.""" - - RMV_REFERENT_NON_REVOC_INTERVAL = "RMV_RFNT_NRI" - RMV_GLOBAL_NON_REVOC_INTERVAL = "RMV_GLB_NRI" - TSTMP_OUT_NON_REVOC_INTRVAL = "TS_OUT_NRI" - CT_UNREVEALED_ATTRIBUTES = "UNRVL_ATTR" - PRES_VALUE_ERROR = "VALUE_ERROR" - PRES_VERIFY_ERROR = "VERIFY_ERROR" - - -class AnonCredsVerifier(ABC, metaclass=ABCMeta): - """Base class for Indy Verifier.""" - - def __repr__(self) -> str: - """ - Return a human readable representation of this class. - - Returns: - A human readable string for this class - - """ - return "<{}>".format(self.__class__.__name__) - - def non_revoc_intervals(self, pres_req: dict, pres: dict, cred_defs: dict) -> list: - """ - 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 - with non-revocable credentials in proof: seek and remove. - - Args: - pres_req: presentation request - pres: corresponding presentation - - """ - msgs = [] - for req_proof_key, pres_key in { - "revealed_attrs": "requested_attributes", - "revealed_attr_groups": "requested_attributes", - "predicates": "requested_predicates", - }.items(): - for uuid, spec in pres["requested_proof"].get(req_proof_key, {}).items(): - if ( - "revocation" - not in cred_defs[ - pres["identifiers"][spec["sub_proof_index"]]["cred_def_id"] - ]["value"] - ): - if uuid in pres_req[pres_key] and pres_req[pres_key][uuid].pop( - "non_revoked", None - ): - msgs.append( - f"{PresVerifyMsg.RMV_REFERENT_NON_REVOC_INTERVAL.value}::" - f"{uuid}" - ) - LOGGER.info( - ( - "Amended presentation request (nonce=%s): removed " - "non-revocation interval at %s referent " - "%s; corresponding credential in proof is irrevocable" - ), - pres_req["nonce"], - pres_key, - uuid, - ) - - if all( - ( - spec.get("timestamp") is None - and "revocation" not in cred_defs[spec["cred_def_id"]]["value"] - ) - for spec in pres["identifiers"] - ): - pres_req.pop("non_revoked", None) - msgs.append(PresVerifyMsg.RMV_GLOBAL_NON_REVOC_INTERVAL.value) - LOGGER.warning( - ( - "Amended presentation request (nonce=%s); removed global " - "non-revocation interval; no revocable credentials in proof" - ), - pres_req["nonce"], - ) - return msgs - - async def check_timestamps( - self, - profile: Profile, - pres_req: Mapping, - pres: Mapping, - rev_reg_defs: Mapping, - ) -> list: - """ - Check for suspicious, missing, and superfluous timestamps. - - Raises ValueError on timestamp in the future, prior to rev reg creation, - superfluous or missing. - - Args: - profile: relevant profile - pres_req: indy proof request - pres: indy 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) - LOGGER.debug(f">>> got non-revoc intervals: {non_revoc_intervals}") - - # timestamp for irrevocable credential - cred_defs: List[GetCredDefResult] = [] - for index, ident in enumerate(pres["identifiers"]): - LOGGER.debug(f">>> got (index, ident): ({index},{ident})") - cred_def_id = ident["cred_def_id"] - anoncreds_registry = profile.inject(AnonCredsRegistry) - cred_def_result = await anoncreds_registry.get_credential_definition( - profile, cred_def_id - ) - cred_defs.append(cred_def_result) - if ident.get("timestamp"): - if not cred_def_result.credential_definition.value.revocation: - raise ValueError( - f"Timestamp in presentation identifier #{index} " - f"for irrevocable cred def id {cred_def_id}" - ) - - # timestamp in the future too far in the past - for ident in pres["identifiers"]: - timestamp = ident.get("timestamp") - rev_reg_id = ident.get("rev_reg_id") - - if not timestamp: - continue - - if timestamp > now + 300: # allow 5 min for clock skew - raise ValueError(f"Timestamp {timestamp} is in the future") - reg_def = rev_reg_defs.get(rev_reg_id) - if not reg_def: - raise ValueError(f"Missing registry definition for '{rev_reg_id}'") - if "txnTime" not in reg_def: - raise ValueError( - f"Missing txnTime for registry definition '{rev_reg_id}'" - ) - if timestamp < reg_def["txnTime"]: - raise ValueError( - f"Timestamp {timestamp} predates rev reg {rev_reg_id} creation" - ) - - # timestamp superfluous, missing, or outside non-revocation interval - revealed_attrs = pres["requested_proof"].get("revealed_attrs", {}) - unrevealed_attrs = pres["requested_proof"].get("unrevealed_attrs", {}) - revealed_groups = pres["requested_proof"].get("revealed_attr_groups", {}) - self_attested = pres["requested_proof"].get("self_attested_attrs", {}) - preds = pres["requested_proof"].get("predicates", {}) - for uuid, req_attr in pres_req["requested_attributes"].items(): - if "name" in req_attr: - if uuid in revealed_attrs: - index = revealed_attrs[uuid]["sub_proof_index"] - if cred_defs[index].credential_definition.value.revocation: - timestamp = pres["identifiers"][index].get("timestamp") - if (timestamp is not None) ^ bool( - non_revoc_intervals.get(uuid) - ): - LOGGER.debug(f">>> uuid: {uuid}") - LOGGER.debug( - f">>> revealed_attrs[uuid]: {revealed_attrs[uuid]}" - ) - raise ValueError( - f"Timestamp on sub-proof #{index} " - f"is {'superfluous' if timestamp else 'missing'} " - f"vs. requested attribute {uuid}" - ) - if non_revoc_intervals.get(uuid) and not ( - non_revoc_intervals[uuid].get("from", 0) - < timestamp - < non_revoc_intervals[uuid].get("to", now) - ): - msgs.append( - f"{PresVerifyMsg.TSTMP_OUT_NON_REVOC_INTRVAL.value}::" - f"{uuid}" - ) - LOGGER.info( - f"Timestamp {timestamp} from ledger for item" - f"{uuid} falls outside non-revocation interval " - f"{non_revoc_intervals[uuid]}" - ) - elif uuid in unrevealed_attrs: - # nothing to do, attribute value is not revealed - msgs.append( - f"{PresVerifyMsg.CT_UNREVEALED_ATTRIBUTES.value}::" f"{uuid}" - ) - elif uuid not in self_attested: - raise ValueError( - f"Presentation attributes mismatch requested attribute {uuid}" - ) - - elif "names" in req_attr: - group_spec = revealed_groups.get(uuid) - if ( - group_spec is None - or "sub_proof_index" not in group_spec - or "values" not in group_spec - ): - raise ValueError(f"Missing requested attribute group {uuid}") - index = group_spec["sub_proof_index"] - if cred_defs[index].credential_definition.value.revocation: - timestamp = pres["identifiers"][index].get("timestamp") - if (timestamp is not None) ^ bool(non_revoc_intervals.get(uuid)): - raise ValueError( - f"Timestamp on sub-proof #{index} " - f"is {'superfluous' if timestamp else 'missing'} " - f"vs. requested attribute group {uuid}" - ) - if non_revoc_intervals.get(uuid) and not ( - non_revoc_intervals[uuid].get("from", 0) - < timestamp - < non_revoc_intervals[uuid].get("to", now) - ): - msgs.append( - f"{PresVerifyMsg.TSTMP_OUT_NON_REVOC_INTRVAL.value}::" - f"{uuid}" - ) - LOGGER.warning( - f"Timestamp {timestamp} from ledger for item" - f"{uuid} falls outside non-revocation interval " - f"{non_revoc_intervals[uuid]}" - ) - - for uuid, req_pred in pres_req["requested_predicates"].items(): - pred_spec = preds.get(uuid) - if pred_spec is None or "sub_proof_index" not in pred_spec: - raise ValueError( - f"Presentation predicates mismatch requested predicate {uuid}" - ) - index = pred_spec["sub_proof_index"] - if cred_defs[index].credential_definition.value.revocation: - timestamp = pres["identifiers"][index].get("timestamp") - if (timestamp is not None) ^ bool(non_revoc_intervals.get(uuid)): - raise ValueError( - f"Timestamp on sub-proof #{index} " - f"is {'superfluous' if timestamp else 'missing'} " - f"vs. requested predicate {uuid}" - ) - if non_revoc_intervals.get(uuid) and not ( - non_revoc_intervals[uuid].get("from", 0) - < timestamp - < non_revoc_intervals[uuid].get("to", now) - ): - msgs.append( - f"{PresVerifyMsg.TSTMP_OUT_NON_REVOC_INTRVAL.value}::" f"{uuid}" - ) - LOGGER.warning( - f"Best-effort timestamp {timestamp} " - "from ledger falls outside non-revocation interval " - f"{non_revoc_intervals[uuid]}" - ) - return msgs - - async def pre_verify(self, pres_req: dict, pres: dict) -> list: - """ - Check for essential components and tampering in presentation. - - Visit encoded attribute values against raw, and predicate bounds, - in presentation, cross-reference against presentation request. - - Args: - pres_req: presentation request - pres: corresponding presentation - - """ - msgs = [] - if not ( - pres_req - and "requested_predicates" in pres_req - and "requested_attributes" in pres_req - ): - raise ValueError("Incomplete or missing proof request") - if not pres: - raise ValueError("No proof provided") - if "requested_proof" not in pres: - raise ValueError("Presentation missing 'requested_proof'") - if "proof" not in pres: - raise ValueError("Presentation missing 'proof'") - - for uuid, req_pred in pres_req["requested_predicates"].items(): - try: - canon_attr = canon(req_pred["name"]) - matched = False - found = False - for ge_proof in pres["proof"]["proofs"][ - pres["requested_proof"]["predicates"][uuid]["sub_proof_index"] - ]["primary_proof"]["ge_proofs"]: - pred = ge_proof["predicate"] - if pred["attr_name"] == canon_attr: - found = True - if pred["value"] == req_pred["p_value"]: - matched = True - break - if not matched: - raise ValueError(f"Predicate value != p_value: {pred['attr_name']}") - break - elif not found: - raise ValueError(f"Missing requested predicate '{uuid}'") - except (KeyError, TypeError): - raise ValueError(f"Missing requested predicate '{uuid}'") - - revealed_attrs = pres["requested_proof"].get("revealed_attrs", {}) - unrevealed_attrs = pres["requested_proof"].get("unrevealed_attrs", {}) - revealed_groups = pres["requested_proof"].get("revealed_attr_groups", {}) - self_attested = pres["requested_proof"].get("self_attested_attrs", {}) - for uuid, req_attr in pres_req["requested_attributes"].items(): - if "name" in req_attr: - if uuid in revealed_attrs: - pres_req_attr_spec = {req_attr["name"]: revealed_attrs[uuid]} - elif uuid in unrevealed_attrs: - # unrevealed attribute, nothing to do - pres_req_attr_spec = {} - msgs.append( - f"{PresVerifyMsg.CT_UNREVEALED_ATTRIBUTES.value}::" f"{uuid}" - ) - elif uuid in self_attested: - if not req_attr.get("restrictions"): - continue - raise ValueError( - "Attribute with restrictions cannot be self-attested: " - f"'{req_attr['name']}'" - ) - else: - raise ValueError( - f"Missing requested attribute '{req_attr['name']}'" - ) - elif "names" in req_attr: - group_spec = revealed_groups[uuid] - pres_req_attr_spec = { - attr: { - "sub_proof_index": group_spec["sub_proof_index"], - **group_spec["values"].get(attr), - } - for attr in req_attr["names"] - } - else: - raise ValueError( - f"Request attribute missing 'name' and 'names': '{uuid}'" - ) - - for attr, spec in pres_req_attr_spec.items(): - try: - primary_enco = pres["proof"]["proofs"][spec["sub_proof_index"]][ - "primary_proof" - ]["eq_proof"]["revealed_attrs"][canon(attr)] - except (KeyError, TypeError): - raise ValueError(f"Missing revealed attribute: '{attr}'") - if primary_enco != spec["encoded"]: - raise ValueError(f"Encoded representation mismatch for '{attr}'") - if primary_enco != encode(spec["raw"]): - raise ValueError(f"Encoded representation mismatch for '{attr}'") - return msgs - - @abstractmethod - async def process_pres_identifiers( - self, - identifiers: list, - ) -> Tuple[dict, dict, dict, dict]: - """Return schemas, cred_defs, rev_reg_defs, rev_status_lists.""" - - @abstractmethod - async def verify_presentation( - self, - presentation_request, - presentation, - schemas, - credential_definitions, - rev_reg_defs, - rev_status_lists, - ) -> Tuple[bool, list]: - """ - Verify a presentation. - - Args: - presentation_request: Presentation request data - presentation: Presentation data - schemas: Schema data - credential_definitions: credential definition data - rev_reg_defs: revocation registry definitions - rev_reg_entries: revocation registry entries - """ diff --git a/aries_cloudagent/askar/profile.py b/aries_cloudagent/askar/profile.py index 8529b3864c..c3c9d48dfe 100644 --- a/aries_cloudagent/askar/profile.py +++ b/aries_cloudagent/askar/profile.py @@ -16,9 +16,9 @@ from ..config.provider import ClassProvider from ..core.error import ProfileError from ..core.profile import Profile, ProfileManager, ProfileSession -from ..anoncreds.holder import AnonCredsHolder -from ..anoncreds.issuer import AnonCredsIssuer -from ..anoncreds.verifier import AnonCredsVerifier +from ..anoncreds.anoncreds.holder import AnonCredsHolder +from ..anoncreds.anoncreds.issuer import AnonCredsIssuer +from ..anoncreds.anoncreds.verifier import AnonCredsVerifier from ..ledger.base import BaseLedger from ..ledger.indy_vdr import IndyVdrLedger, IndyVdrLedgerPool from ..storage.base import BaseStorage, BaseStorageSearch @@ -103,14 +103,14 @@ def bind_providers(self): injector.bind_provider( AnonCredsHolder, ClassProvider( - "aries_cloudagent.anoncreds.anoncreds.holder.AnonCredsRsHolder", + "aries_cloudagent.anoncreds.anoncreds.holder.AnonCredsHolder", ref(self), ), ) injector.bind_provider( AnonCredsIssuer, ClassProvider( - "aries_cloudagent.anoncreds.anoncreds.issuer.AnonCredsRsIssuer", + "aries_cloudagent.anoncreds.anoncreds.issuer.AnonCredsIssuer", ref(self), ), ) @@ -130,7 +130,7 @@ def bind_providers(self): injector.bind_provider( AnonCredsVerifier, ClassProvider( - "aries_cloudagent.anoncreds.anoncreds.verifier.AnonCredsRsVerifier", + "aries_cloudagent.anoncreds.anoncreds.verifier.AnonCredsVerifier", ref(self), ), ) diff --git a/aries_cloudagent/core/conductor.py b/aries_cloudagent/core/conductor.py index 6699f7a38e..96cb11a45e 100644 --- a/aries_cloudagent/core/conductor.py +++ b/aries_cloudagent/core/conductor.py @@ -27,7 +27,7 @@ from ..config.provider import ClassProvider from ..config.wallet import wallet_config from ..core.profile import Profile -from ..anoncreds.verifier import AnonCredsVerifier +from ..anoncreds.anoncreds.verifier import AnonCredsVerifier from ..ledger.error import LedgerConfigError, LedgerTransactionError from ..ledger.multiple_ledger.base_manager import ( diff --git a/aries_cloudagent/holder/routes.py b/aries_cloudagent/holder/routes.py index e05cfec6a1..f1ead02307 100644 --- a/aries_cloudagent/holder/routes.py +++ b/aries_cloudagent/holder/routes.py @@ -13,7 +13,7 @@ from marshmallow import fields from ..admin.request_context import AdminRequestContext -from ..anoncreds.holder import AnonCredsHolder, AnonCredsHolderError +from ..anoncreds.anoncreds.holder import AnonCredsHolder, AnonCredsHolderError from ..anoncreds.models.cred_precis import IndyCredInfoSchema from ..ledger.base import BaseLedger from ..ledger.error import LedgerError diff --git a/aries_cloudagent/holder/tests/test_routes.py b/aries_cloudagent/holder/tests/test_routes.py index 4150a81636..49ecca6cf6 100644 --- a/aries_cloudagent/holder/tests/test_routes.py +++ b/aries_cloudagent/holder/tests/test_routes.py @@ -8,7 +8,7 @@ from ...wallet.base import BaseWallet from ...admin.request_context import AdminRequestContext -from ...anoncreds.holder import AnonCredsHolder +from ...anoncreds.anoncreds.holder import AnonCredsHolder from ...ledger.base import BaseLedger from ...storage.vc_holder.base import VCHolder from ...storage.vc_holder.vc_record import VCRecord diff --git a/aries_cloudagent/ledger/base.py b/aries_cloudagent/ledger/base.py index 8a780dde98..b2cc4a0780 100644 --- a/aries_cloudagent/ledger/base.py +++ b/aries_cloudagent/ledger/base.py @@ -9,7 +9,7 @@ from hashlib import sha256 from typing import List, Optional, Sequence, Tuple, Union -from ..anoncreds.issuer import ( +from ..anoncreds.anoncreds.issuer import ( DEFAULT_CRED_DEF_TAG, AnonCredsIssuer, AnonCredsIssuerError, diff --git a/aries_cloudagent/ledger/tests/test_indy.py b/aries_cloudagent/ledger/tests/test_indy.py index b48c82bae3..d2f122e43f 100644 --- a/aries_cloudagent/ledger/tests/test_indy.py +++ b/aries_cloudagent/ledger/tests/test_indy.py @@ -9,7 +9,7 @@ from ...config.injection_context import InjectionContext from ...cache.in_memory import InMemoryCache -from ...anoncreds.issuer import AnonCredsIssuer, AnonCredsIssuerError +from ...anoncreds.anoncreds.issuer import AnonCredsIssuer, AnonCredsIssuerError from ...anoncreds.sdk.profile import IndySdkProfile from ...storage.record import StorageRecord from ...wallet.base import BaseWallet @@ -1845,8 +1845,8 @@ async def test_send_credential_definition_create_cred_def_exception( mock_fetch_cred_def.return_value = None issuer = async_mock.MagicMock(AnonCredsIssuer) - issuer.create_and_store_credential_definition.side_effect = AnonCredsIssuerError( - "invalid structure" + issuer.create_and_store_credential_definition.side_effect = ( + AnonCredsIssuerError("invalid structure") ) ledger = IndySdkLedger(IndySdkLedgerPool("name", checked=True), self.profile) schema_id = "schema_issuer_did:name:1.0" diff --git a/aries_cloudagent/ledger/tests/test_indy_vdr.py b/aries_cloudagent/ledger/tests/test_indy_vdr.py index 484ffbe297..02fc023dfe 100644 --- a/aries_cloudagent/ledger/tests/test_indy_vdr.py +++ b/aries_cloudagent/ledger/tests/test_indy_vdr.py @@ -7,7 +7,7 @@ import indy_vdr from ...core.in_memory import InMemoryProfile -from ...anoncreds.issuer import AnonCredsIssuer +from ...anoncreds.anoncreds.issuer import AnonCredsIssuer from ...wallet.base import BaseWallet from ...wallet.key_type import KeyType, ED25519 from ...wallet.did_method import SOV, DIDMethods diff --git a/aries_cloudagent/messaging/credential_definitions/routes.py b/aries_cloudagent/messaging/credential_definitions/routes.py index c75f976a2f..b3a4253e9e 100644 --- a/aries_cloudagent/messaging/credential_definitions/routes.py +++ b/aries_cloudagent/messaging/credential_definitions/routes.py @@ -20,7 +20,7 @@ from ...admin.request_context import AdminRequestContext from ...core.event_bus import Event, EventBus from ...core.profile import Profile -from ...anoncreds.issuer import AnonCredsIssuer, AnonCredsIssuerError +from ...anoncreds.anoncreds.issuer import AnonCredsIssuer, AnonCredsIssuerError from ...anoncreds.models.cred_def import CredentialDefinitionSchema from ...ledger.base import BaseLedger from ...ledger.error import LedgerError diff --git a/aries_cloudagent/messaging/credential_definitions/tests/test_routes.py b/aries_cloudagent/messaging/credential_definitions/tests/test_routes.py index 75bfc15534..a5b14a6f1f 100644 --- a/aries_cloudagent/messaging/credential_definitions/tests/test_routes.py +++ b/aries_cloudagent/messaging/credential_definitions/tests/test_routes.py @@ -3,7 +3,7 @@ from ....admin.request_context import AdminRequestContext from ....core.in_memory import InMemoryProfile -from ....anoncreds.issuer import AnonCredsIssuer +from ....anoncreds.anoncreds.issuer import AnonCredsIssuer from ....ledger.base import BaseLedger from ....ledger.multiple_ledger.ledger_requests_executor import ( IndyLedgerRequestsExecutor, diff --git a/aries_cloudagent/messaging/schemas/routes.py b/aries_cloudagent/messaging/schemas/routes.py index 957b3f0a25..012c53a9ab 100644 --- a/aries_cloudagent/messaging/schemas/routes.py +++ b/aries_cloudagent/messaging/schemas/routes.py @@ -16,7 +16,7 @@ from marshmallow.validate import Regexp from ...admin.request_context import AdminRequestContext -from ...anoncreds.issuer import AnonCredsIssuer, AnonCredsIssuerError +from ...anoncreds.anoncreds.issuer import AnonCredsIssuer, AnonCredsIssuerError from ...connections.models.conn_record import ConnRecord from ...core.event_bus import Event, EventBus from ...core.profile import Profile diff --git a/aries_cloudagent/messaging/schemas/tests/test_routes.py b/aries_cloudagent/messaging/schemas/tests/test_routes.py index 568cc96c6a..62731b672e 100644 --- a/aries_cloudagent/messaging/schemas/tests/test_routes.py +++ b/aries_cloudagent/messaging/schemas/tests/test_routes.py @@ -3,7 +3,7 @@ from ....admin.request_context import AdminRequestContext from ....core.in_memory import InMemoryProfile -from ....anoncreds.issuer import AnonCredsIssuer +from ....anoncreds.anoncreds.issuer import AnonCredsIssuer from ....ledger.base import BaseLedger from ....ledger.multiple_ledger.ledger_requests_executor import ( IndyLedgerRequestsExecutor, diff --git a/aries_cloudagent/protocols/endorse_transaction/v1_0/manager.py b/aries_cloudagent/protocols/endorse_transaction/v1_0/manager.py index 1b4cd172f8..6eefdcb2fc 100644 --- a/aries_cloudagent/protocols/endorse_transaction/v1_0/manager.py +++ b/aries_cloudagent/protocols/endorse_transaction/v1_0/manager.py @@ -9,7 +9,7 @@ from ....connections.models.conn_record import ConnRecord from ....core.error import BaseError from ....core.profile import Profile -from ....anoncreds.issuer import AnonCredsIssuerError +from ....anoncreds.anoncreds.issuer import AnonCredsIssuerError from ....ledger.base import BaseLedger from ....ledger.error import LedgerError from ....messaging.credential_definitions.util import notify_cred_def_event diff --git a/aries_cloudagent/protocols/endorse_transaction/v1_0/routes.py b/aries_cloudagent/protocols/endorse_transaction/v1_0/routes.py index 559cd025c2..235669b1e8 100644 --- a/aries_cloudagent/protocols/endorse_transaction/v1_0/routes.py +++ b/aries_cloudagent/protocols/endorse_transaction/v1_0/routes.py @@ -18,7 +18,7 @@ from ....core.event_bus import Event, EventBus from ....core.profile import Profile from ....core.util import STARTUP_EVENT_PATTERN, SHUTDOWN_EVENT_PATTERN -from ....anoncreds.issuer import AnonCredsIssuerError +from ....anoncreds.anoncreds.issuer import AnonCredsIssuerError from ....ledger.error import LedgerError from ....messaging.models.base import BaseModelError from ....messaging.models.openapi import OpenAPISchema diff --git a/aries_cloudagent/protocols/issue_credential/v1_0/handlers/credential_issue_handler.py b/aries_cloudagent/protocols/issue_credential/v1_0/handlers/credential_issue_handler.py index 2ec672c086..dab8c77b34 100644 --- a/aries_cloudagent/protocols/issue_credential/v1_0/handlers/credential_issue_handler.py +++ b/aries_cloudagent/protocols/issue_credential/v1_0/handlers/credential_issue_handler.py @@ -1,7 +1,7 @@ """Credential issue message handler.""" from .....core.oob_processor import OobMessageProcessor -from .....anoncreds.holder import AnonCredsHolderError +from .....anoncreds.anoncreds.holder import AnonCredsHolderError from .....messaging.base_handler import BaseHandler, HandlerException from .....messaging.models.base import BaseModelError from .....messaging.request_context import RequestContext diff --git a/aries_cloudagent/protocols/issue_credential/v1_0/handlers/credential_offer_handler.py b/aries_cloudagent/protocols/issue_credential/v1_0/handlers/credential_offer_handler.py index fe5d39b099..4f90fa6888 100644 --- a/aries_cloudagent/protocols/issue_credential/v1_0/handlers/credential_offer_handler.py +++ b/aries_cloudagent/protocols/issue_credential/v1_0/handlers/credential_offer_handler.py @@ -3,7 +3,7 @@ from .....wallet.util import default_did_from_verkey from .....core.oob_processor import OobMessageProcessor -from .....anoncreds.holder import AnonCredsHolderError +from .....anoncreds.anoncreds.holder import AnonCredsHolderError from .....ledger.error import LedgerError from .....messaging.base_handler import BaseHandler, HandlerException from .....messaging.models.base import BaseModelError diff --git a/aries_cloudagent/protocols/issue_credential/v1_0/handlers/credential_proposal_handler.py b/aries_cloudagent/protocols/issue_credential/v1_0/handlers/credential_proposal_handler.py index 71401296ec..c6736479ac 100644 --- a/aries_cloudagent/protocols/issue_credential/v1_0/handlers/credential_proposal_handler.py +++ b/aries_cloudagent/protocols/issue_credential/v1_0/handlers/credential_proposal_handler.py @@ -1,6 +1,6 @@ """Credential proposal message handler.""" -from .....anoncreds.issuer import AnonCredsIssuerError +from .....anoncreds.anoncreds.issuer import AnonCredsIssuerError from .....ledger.error import LedgerError from .....messaging.base_handler import BaseHandler, HandlerException from .....messaging.models.base import BaseModelError diff --git a/aries_cloudagent/protocols/issue_credential/v1_0/handlers/credential_request_handler.py b/aries_cloudagent/protocols/issue_credential/v1_0/handlers/credential_request_handler.py index ff9b363313..0cef4cc937 100644 --- a/aries_cloudagent/protocols/issue_credential/v1_0/handlers/credential_request_handler.py +++ b/aries_cloudagent/protocols/issue_credential/v1_0/handlers/credential_request_handler.py @@ -1,7 +1,7 @@ """Credential request message handler.""" from .....core.oob_processor import OobMessageProcessor -from .....anoncreds.issuer import AnonCredsIssuerError +from .....anoncreds.anoncreds.issuer import AnonCredsIssuerError from .....ledger.error import LedgerError from .....messaging.base_handler import BaseHandler, HandlerException from .....messaging.models.base import BaseModelError diff --git a/aries_cloudagent/protocols/issue_credential/v1_0/manager.py b/aries_cloudagent/protocols/issue_credential/v1_0/manager.py index 88463924b9..a5a9c5f759 100644 --- a/aries_cloudagent/protocols/issue_credential/v1_0/manager.py +++ b/aries_cloudagent/protocols/issue_credential/v1_0/manager.py @@ -10,8 +10,8 @@ from ....connections.models.conn_record import ConnRecord from ....core.error import BaseError from ....core.profile import Profile -from ....anoncreds.holder import AnonCredsHolder, AnonCredsHolderError -from ....anoncreds.issuer import ( +from ....anoncreds.anoncreds.holder import AnonCredsHolder, AnonCredsHolderError +from ....anoncreds.anoncreds.issuer import ( AnonCredsIssuer, AnonCredsIssuerRevocationRegistryFullError, ) diff --git a/aries_cloudagent/protocols/issue_credential/v1_0/routes.py b/aries_cloudagent/protocols/issue_credential/v1_0/routes.py index 0d385bdf82..bf5b8947f5 100644 --- a/aries_cloudagent/protocols/issue_credential/v1_0/routes.py +++ b/aries_cloudagent/protocols/issue_credential/v1_0/routes.py @@ -16,8 +16,8 @@ from ....admin.request_context import AdminRequestContext from ....connections.models.conn_record import ConnRecord from ....core.profile import Profile -from ....anoncreds.holder import AnonCredsHolderError -from ....anoncreds.issuer import AnonCredsIssuerError +from ....anoncreds.anoncreds.holder import AnonCredsHolderError +from ....anoncreds.anoncreds.issuer import AnonCredsIssuerError from ....ledger.error import LedgerError from ....messaging.credential_definitions.util import CRED_DEF_TAGS from ....messaging.models.base import BaseModelError diff --git a/aries_cloudagent/protocols/issue_credential/v1_0/tests/test_manager.py b/aries_cloudagent/protocols/issue_credential/v1_0/tests/test_manager.py index c9ca5565c3..a0a44c71d9 100644 --- a/aries_cloudagent/protocols/issue_credential/v1_0/tests/test_manager.py +++ b/aries_cloudagent/protocols/issue_credential/v1_0/tests/test_manager.py @@ -10,8 +10,8 @@ from .....core.in_memory import InMemoryProfile from .....cache.base import BaseCache from .....cache.in_memory import InMemoryCache -from .....anoncreds.holder import AnonCredsHolder -from .....anoncreds.issuer import AnonCredsIssuer +from .....anoncreds.anoncreds.holder import AnonCredsHolder +from .....anoncreds.anoncreds.issuer import AnonCredsIssuer from .....messaging.decorators.thread_decorator import ThreadDecorator from .....messaging.credential_definitions.util import CRED_DEF_SENT_RECORD_TYPE from .....messaging.responder import BaseResponder, MockResponder diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/handler.py b/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/handler.py index 26b7b708f6..21af194883 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/handler.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/handler.py @@ -8,8 +8,8 @@ from marshmallow import RAISE from ......anoncreds.anoncreds.registry import AnonCredsRegistry -from ......anoncreds.holder import AnonCredsHolder, AnonCredsHolderError -from ......anoncreds.issuer import ( +from ......anoncreds.anoncreds.holder import AnonCredsHolder, AnonCredsHolderError +from ......anoncreds.anoncreds.issuer import ( AnonCredsIssuer, AnonCredsIssuerRevocationRegistryFullError, ) diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/tests/test_handler.py b/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/tests/test_handler.py index 80a98d2c71..4a6d5be50b 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/tests/test_handler.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/tests/test_handler.py @@ -16,14 +16,14 @@ ) from .......multitenant.base import BaseMultitenantManager from .......multitenant.manager import MultitenantManager -from .......anoncreds.issuer import AnonCredsIssuer +from .......anoncreds.anoncreds.issuer import AnonCredsIssuer from .......cache.in_memory import InMemoryCache from .......cache.base import BaseCache from .......storage.record import StorageRecord from .......storage.error import StorageNotFoundError from .......messaging.credential_definitions.util import CRED_DEF_SENT_RECORD_TYPE from .......messaging.decorators.attach_decorator import AttachDecorator -from .......anoncreds.holder import AnonCredsHolder +from .......anoncreds.anoncreds.holder import AnonCredsHolder from ....models.detail.indy import V20CredExRecordIndy from ....messages.cred_proposal import V20CredProposal from ....messages.cred_format import V20CredFormat diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/handlers/cred_issue_handler.py b/aries_cloudagent/protocols/issue_credential/v2_0/handlers/cred_issue_handler.py index 8e88fa4c9f..7123f221e5 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/handlers/cred_issue_handler.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/handlers/cred_issue_handler.py @@ -1,7 +1,7 @@ """Credential issue message handler.""" from .....core.oob_processor import OobMessageProcessor -from .....anoncreds.holder import AnonCredsHolderError +from .....anoncreds.anoncreds.holder import AnonCredsHolderError from .....messaging.base_handler import BaseHandler, HandlerException from .....messaging.models.base import BaseModelError from .....messaging.request_context import RequestContext diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/handlers/cred_offer_handler.py b/aries_cloudagent/protocols/issue_credential/v2_0/handlers/cred_offer_handler.py index 1b1e0e532c..0b432fc16e 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/handlers/cred_offer_handler.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/handlers/cred_offer_handler.py @@ -2,7 +2,7 @@ from .....wallet.util import default_did_from_verkey from .....core.oob_processor import OobMessageProcessor -from .....anoncreds.holder import AnonCredsHolderError +from .....anoncreds.anoncreds.holder import AnonCredsHolderError from .....ledger.error import LedgerError from .....messaging.base_handler import BaseHandler, HandlerException from .....messaging.models.base import BaseModelError diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/handlers/cred_proposal_handler.py b/aries_cloudagent/protocols/issue_credential/v2_0/handlers/cred_proposal_handler.py index 4dc7dafdcd..46a86a5f79 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/handlers/cred_proposal_handler.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/handlers/cred_proposal_handler.py @@ -1,6 +1,6 @@ """Credential proposal message handler.""" -from .....anoncreds.issuer import AnonCredsIssuerError +from .....anoncreds.anoncreds.issuer import AnonCredsIssuerError from .....ledger.error import LedgerError from .....messaging.base_handler import BaseHandler, HandlerException from .....messaging.models.base import BaseModelError diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/handlers/cred_request_handler.py b/aries_cloudagent/protocols/issue_credential/v2_0/handlers/cred_request_handler.py index af1ab58956..43c80f36d2 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/handlers/cred_request_handler.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/handlers/cred_request_handler.py @@ -1,7 +1,7 @@ """Credential request message handler.""" from .....core.oob_processor import OobMessageProcessor -from .....anoncreds.issuer import AnonCredsIssuerError +from .....anoncreds.anoncreds.issuer import AnonCredsIssuerError from .....ledger.error import LedgerError from .....messaging.base_handler import BaseHandler, HandlerException from .....messaging.models.base import BaseModelError diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/routes.py b/aries_cloudagent/protocols/issue_credential/v2_0/routes.py index 735dd9250a..d6b0a481a8 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/routes.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/routes.py @@ -20,8 +20,8 @@ from ....admin.request_context import AdminRequestContext from ....connections.models.conn_record import ConnRecord from ....core.profile import Profile -from ....anoncreds.holder import AnonCredsHolderError -from ....anoncreds.issuer import AnonCredsIssuerError +from ....anoncreds.anoncreds.holder import AnonCredsHolderError +from ....anoncreds.anoncreds.issuer import AnonCredsIssuerError from ....ledger.error import LedgerError from ....messaging.decorators.attach_decorator import AttachDecorator from ....messaging.models.base import BaseModelError diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/tests/test_manager.py b/aries_cloudagent/protocols/issue_credential/v2_0/tests/test_manager.py index 45b42b47be..4758c6a81e 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/tests/test_manager.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/tests/test_manager.py @@ -9,7 +9,7 @@ from .....cache.base import BaseCache from .....cache.in_memory import InMemoryCache from .....core.in_memory import InMemoryProfile -from .....anoncreds.issuer import AnonCredsIssuer +from .....anoncreds.anoncreds.issuer import AnonCredsIssuer from .....messaging.decorators.thread_decorator import ThreadDecorator from .....messaging.decorators.attach_decorator import AttachDecorator from .....messaging.responder import BaseResponder, MockResponder diff --git a/aries_cloudagent/protocols/present_proof/indy/pres_exch_handler.py b/aries_cloudagent/protocols/present_proof/indy/pres_exch_handler.py index 77fd24a21d..1b39e29446 100644 --- a/aries_cloudagent/protocols/present_proof/indy/pres_exch_handler.py +++ b/aries_cloudagent/protocols/present_proof/indy/pres_exch_handler.py @@ -5,7 +5,7 @@ from typing import Union from ....anoncreds.anoncreds.registry import AnonCredsRegistry -from ....anoncreds.holder import AnonCredsHolder, AnonCredsHolderError +from ....anoncreds.anoncreds.holder import AnonCredsHolder, AnonCredsHolderError from ....anoncreds.models.xform import indy_proof_req2non_revoc_intervals from ....core.error import BaseError from ....core.profile import Profile diff --git a/aries_cloudagent/protocols/present_proof/v1_0/handlers/presentation_request_handler.py b/aries_cloudagent/protocols/present_proof/v1_0/handlers/presentation_request_handler.py index c49f4ba2fd..16ebcac9c5 100644 --- a/aries_cloudagent/protocols/present_proof/v1_0/handlers/presentation_request_handler.py +++ b/aries_cloudagent/protocols/present_proof/v1_0/handlers/presentation_request_handler.py @@ -1,7 +1,7 @@ """Presentation request message handler.""" from .....core.oob_processor import OobMessageProcessor -from .....anoncreds.holder import AnonCredsHolder, AnonCredsHolderError +from .....anoncreds.anoncreds.holder import AnonCredsHolder, AnonCredsHolderError from .....anoncreds.models.xform import indy_proof_req_preview2indy_requested_creds from .....ledger.error import LedgerError from .....messaging.base_handler import BaseHandler, HandlerException diff --git a/aries_cloudagent/protocols/present_proof/v1_0/handlers/tests/test_presentation_request_handler.py b/aries_cloudagent/protocols/present_proof/v1_0/handlers/tests/test_presentation_request_handler.py index a834cd40d3..bfc3f6310d 100644 --- a/aries_cloudagent/protocols/present_proof/v1_0/handlers/tests/test_presentation_request_handler.py +++ b/aries_cloudagent/protocols/present_proof/v1_0/handlers/tests/test_presentation_request_handler.py @@ -2,7 +2,7 @@ from ......core.oob_processor import OobMessageProcessor -from ......anoncreds.holder import AnonCredsHolder +from ......anoncreds.anoncreds.holder import AnonCredsHolder from ......anoncreds.models.pres_preview import ( IndyPresAttrSpec, IndyPresPredSpec, diff --git a/aries_cloudagent/protocols/present_proof/v1_0/manager.py b/aries_cloudagent/protocols/present_proof/v1_0/manager.py index 208483a2c8..e2cf7f1c89 100644 --- a/aries_cloudagent/protocols/present_proof/v1_0/manager.py +++ b/aries_cloudagent/protocols/present_proof/v1_0/manager.py @@ -8,7 +8,7 @@ from ....connections.models.conn_record import ConnRecord from ....core.error import BaseError from ....core.profile import Profile -from ....anoncreds.verifier import AnonCredsVerifier +from ....anoncreds.anoncreds.verifier import AnonCredsVerifier from ....messaging.decorators.attach_decorator import AttachDecorator from ....messaging.responder import BaseResponder from ....storage.error import StorageNotFoundError diff --git a/aries_cloudagent/protocols/present_proof/v1_0/routes.py b/aries_cloudagent/protocols/present_proof/v1_0/routes.py index 901abc86fd..36b01baed0 100644 --- a/aries_cloudagent/protocols/present_proof/v1_0/routes.py +++ b/aries_cloudagent/protocols/present_proof/v1_0/routes.py @@ -14,7 +14,7 @@ from ....admin.request_context import AdminRequestContext from ....connections.models.conn_record import ConnRecord -from ....anoncreds.holder import AnonCredsHolder, AnonCredsHolderError +from ....anoncreds.anoncreds.holder import AnonCredsHolder, AnonCredsHolderError from ....anoncreds.models.cred_precis import IndyCredPrecisSchema from ....anoncreds.models.proof import IndyPresSpecSchema from ....anoncreds.models.proof_request import IndyProofRequestSchema diff --git a/aries_cloudagent/protocols/present_proof/v1_0/tests/test_manager.py b/aries_cloudagent/protocols/present_proof/v1_0/tests/test_manager.py index 452c89b0b6..8807c96018 100644 --- a/aries_cloudagent/protocols/present_proof/v1_0/tests/test_manager.py +++ b/aries_cloudagent/protocols/present_proof/v1_0/tests/test_manager.py @@ -9,17 +9,15 @@ ) from .....core.in_memory import InMemoryProfile -from .....anoncreds.holder import AnonCredsHolder, AnonCredsHolderError -from .....anoncreds.issuer import AnonCredsIssuer -from .....anoncreds.sdk.holder import IndySdkHolder +from .....anoncreds.anoncreds.holder import AnonCredsHolder, AnonCredsHolderError +from .....anoncreds.anoncreds.issuer import AnonCredsIssuer from .....anoncreds.models.xform import indy_proof_req_preview2indy_requested_creds from .....anoncreds.models.pres_preview import ( IndyPresAttrSpec, IndyPresPreview, IndyPresPredSpec, ) -from .....anoncreds.sdk.verifier import IndySdkVerifier -from .....anoncreds.verifier import AnonCredsVerifier +from .....anoncreds.anoncreds.verifier import AnonCredsVerifier from .....ledger.base import BaseLedger from .....ledger.multiple_ledger.ledger_requests_executor import ( IndyLedgerRequestsExecutor, diff --git a/aries_cloudagent/protocols/present_proof/v1_0/tests/test_routes.py b/aries_cloudagent/protocols/present_proof/v1_0/tests/test_routes.py index a9e859ace1..10e972a780 100644 --- a/aries_cloudagent/protocols/present_proof/v1_0/tests/test_routes.py +++ b/aries_cloudagent/protocols/present_proof/v1_0/tests/test_routes.py @@ -5,9 +5,9 @@ from marshmallow import ValidationError from .....admin.request_context import AdminRequestContext -from .....anoncreds.holder import AnonCredsHolder +from .....anoncreds.anoncreds.holder import AnonCredsHolder from .....anoncreds.models.proof_request import IndyProofReqAttrSpecSchema -from .....anoncreds.verifier import AnonCredsVerifier +from .....anoncreds.anoncreds.verifier import AnonCredsVerifier from .....ledger.base import BaseLedger from .....storage.error import StorageNotFoundError @@ -142,7 +142,9 @@ async def test_presentation_exchange_credentials_x(self): AnonCredsHolder, async_mock.MagicMock( get_credentials_for_presentation_request_by_referent=( - async_mock.CoroutineMock(side_effect=test_module.AnonCredsHolderError()) + async_mock.CoroutineMock( + side_effect=test_module.AnonCredsHolderError() + ) ) ), ) diff --git a/aries_cloudagent/protocols/present_proof/v2_0/formats/indy/handler.py b/aries_cloudagent/protocols/present_proof/v2_0/formats/indy/handler.py index 0c2dbe6be4..bc85987ec3 100644 --- a/aries_cloudagent/protocols/present_proof/v2_0/formats/indy/handler.py +++ b/aries_cloudagent/protocols/present_proof/v2_0/formats/indy/handler.py @@ -6,13 +6,13 @@ from marshmallow import RAISE from typing import Mapping, Tuple -from ......anoncreds.holder import AnonCredsHolder +from ......anoncreds.anoncreds.holder import AnonCredsHolder from ......anoncreds.models.predicate import Predicate from ......anoncreds.models.proof import IndyProofSchema from ......anoncreds.models.proof_request import IndyProofRequestSchema from ......anoncreds.models.xform import indy_proof_req_preview2indy_requested_creds from ......anoncreds.util import generate_pr_nonce -from ......anoncreds.verifier import AnonCredsVerifier +from ......anoncreds.anoncreds.verifier import AnonCredsVerifier from ......messaging.decorators.attach_decorator import AttachDecorator from ......messaging.util import canon diff --git a/aries_cloudagent/protocols/present_proof/v2_0/handlers/pres_request_handler.py b/aries_cloudagent/protocols/present_proof/v2_0/handlers/pres_request_handler.py index 6ad3729c51..4e0195b4ec 100644 --- a/aries_cloudagent/protocols/present_proof/v2_0/handlers/pres_request_handler.py +++ b/aries_cloudagent/protocols/present_proof/v2_0/handlers/pres_request_handler.py @@ -1,7 +1,7 @@ """Presentation request message handler.""" from .....core.oob_processor import OobMessageProcessor -from .....anoncreds.holder import AnonCredsHolderError +from .....anoncreds.anoncreds.holder import AnonCredsHolderError from .....ledger.error import LedgerError from .....messaging.base_handler import BaseHandler, HandlerException from .....messaging.models.base import BaseModelError diff --git a/aries_cloudagent/protocols/present_proof/v2_0/handlers/tests/test_pres_request_handler.py b/aries_cloudagent/protocols/present_proof/v2_0/handlers/tests/test_pres_request_handler.py index 18e49355ca..e38511afff 100644 --- a/aries_cloudagent/protocols/present_proof/v2_0/handlers/tests/test_pres_request_handler.py +++ b/aries_cloudagent/protocols/present_proof/v2_0/handlers/tests/test_pres_request_handler.py @@ -2,7 +2,7 @@ from copy import deepcopy from ......core.oob_processor import OobMessageProcessor -from ......anoncreds.holder import AnonCredsHolder +from ......anoncreds.anoncreds.holder import AnonCredsHolder from ......messaging.decorators.attach_decorator import AttachDecorator from ......messaging.request_context import RequestContext from ......messaging.responder import MockResponder diff --git a/aries_cloudagent/protocols/present_proof/v2_0/routes.py b/aries_cloudagent/protocols/present_proof/v2_0/routes.py index 4a521b6a55..921cf310ba 100644 --- a/aries_cloudagent/protocols/present_proof/v2_0/routes.py +++ b/aries_cloudagent/protocols/present_proof/v2_0/routes.py @@ -15,7 +15,7 @@ from ....admin.request_context import AdminRequestContext from ....connections.models.conn_record import ConnRecord -from ....anoncreds.holder import AnonCredsHolder, AnonCredsHolderError +from ....anoncreds.anoncreds.holder import AnonCredsHolder, AnonCredsHolderError from ....anoncreds.models.cred_precis import IndyCredPrecisSchema from ....anoncreds.models.proof import IndyPresSpecSchema from ....anoncreds.models.proof_request import IndyProofRequestSchema diff --git a/aries_cloudagent/protocols/present_proof/v2_0/tests/test_manager.py b/aries_cloudagent/protocols/present_proof/v2_0/tests/test_manager.py index f8130b83e6..e61e880791 100644 --- a/aries_cloudagent/protocols/present_proof/v2_0/tests/test_manager.py +++ b/aries_cloudagent/protocols/present_proof/v2_0/tests/test_manager.py @@ -6,14 +6,14 @@ from asynctest import mock as async_mock, TestCase as AsyncTestCase from .....core.in_memory import InMemoryProfile -from .....anoncreds.holder import AnonCredsHolder +from .....anoncreds.anoncreds.holder import AnonCredsHolder from .....anoncreds.models.xform import indy_proof_req_preview2indy_requested_creds from .....anoncreds.models.pres_preview import ( IndyPresAttrSpec, IndyPresPreview, IndyPresPredSpec, ) -from .....anoncreds.verifier import AnonCredsVerifier +from .....anoncreds.anoncreds.verifier import AnonCredsVerifier from .....ledger.base import BaseLedger from .....ledger.multiple_ledger.ledger_requests_executor import ( IndyLedgerRequestsExecutor, diff --git a/aries_cloudagent/protocols/present_proof/v2_0/tests/test_routes.py b/aries_cloudagent/protocols/present_proof/v2_0/tests/test_routes.py index 97c4b7360a..12e3417c82 100644 --- a/aries_cloudagent/protocols/present_proof/v2_0/tests/test_routes.py +++ b/aries_cloudagent/protocols/present_proof/v2_0/tests/test_routes.py @@ -6,9 +6,9 @@ from unittest.mock import ANY from .....admin.request_context import AdminRequestContext -from .....anoncreds.holder import AnonCredsHolder +from .....anoncreds.anoncreds.holder import AnonCredsHolder from .....anoncreds.models.proof_request import IndyProofReqAttrSpecSchema -from .....anoncreds.verifier import AnonCredsVerifier +from .....anoncreds.anoncreds.verifier import AnonCredsVerifier from .....ledger.base import BaseLedger from .....storage.error import StorageNotFoundError from .....storage.vc_holder.base import VCHolder @@ -311,7 +311,9 @@ async def test_present_proof_credentials_x(self): AnonCredsHolder, async_mock.MagicMock( get_credentials_for_presentation_request_by_referent=( - async_mock.CoroutineMock(side_effect=test_module.AnonCredsHolderError()) + async_mock.CoroutineMock( + side_effect=test_module.AnonCredsHolderError() + ) ) ), ) diff --git a/aries_cloudagent/revocation/manager.py b/aries_cloudagent/revocation/manager.py index ca891123bb..834fccb565 100644 --- a/aries_cloudagent/revocation/manager.py +++ b/aries_cloudagent/revocation/manager.py @@ -9,7 +9,7 @@ ) from ..core.error import BaseError from ..core.profile import Profile -from ..anoncreds.issuer import AnonCredsIssuer +from ..anoncreds.anoncreds.issuer import AnonCredsIssuer from ..storage.error import StorageNotFoundError from .indy import IndyRevocation from .models.issuer_cred_rev_record import IssuerCredRevRecord diff --git a/aries_cloudagent/revocation/models/issuer_rev_reg_record.py b/aries_cloudagent/revocation/models/issuer_rev_reg_record.py index 29658d59bb..79780847b9 100644 --- a/aries_cloudagent/revocation/models/issuer_rev_reg_record.py +++ b/aries_cloudagent/revocation/models/issuer_rev_reg_record.py @@ -13,7 +13,7 @@ from marshmallow import fields, validate from ...core.profile import Profile, ProfileSession -from ...anoncreds.issuer import AnonCredsIssuer, AnonCredsIssuerError +from ...anoncreds.anoncreds.issuer import AnonCredsIssuer, AnonCredsIssuerError from ...anoncreds.models.revocation import ( IndyRevRegDef, IndyRevRegDefSchema, diff --git a/aries_cloudagent/revocation/models/tests/test_issuer_rev_reg_record.py b/aries_cloudagent/revocation/models/tests/test_issuer_rev_reg_record.py index 5390b47dc5..24abc2b5e7 100644 --- a/aries_cloudagent/revocation/models/tests/test_issuer_rev_reg_record.py +++ b/aries_cloudagent/revocation/models/tests/test_issuer_rev_reg_record.py @@ -5,7 +5,7 @@ from asynctest import TestCase as AsyncTestCase, mock as async_mock from ....core.in_memory import InMemoryProfile -from ....anoncreds.issuer import AnonCredsIssuer, AnonCredsIssuerError +from ....anoncreds.anoncreds.issuer import AnonCredsIssuer, AnonCredsIssuerError from ....anoncreds.models.revocation import IndyRevRegDef from ....anoncreds.util import indy_client_dir from ....ledger.base import BaseLedger diff --git a/aries_cloudagent/revocation/routes.py b/aries_cloudagent/revocation/routes.py index 69f9230dee..da1e519c25 100644 --- a/aries_cloudagent/revocation/routes.py +++ b/aries_cloudagent/revocation/routes.py @@ -22,7 +22,7 @@ from ..connections.models.conn_record import ConnRecord from ..core.event_bus import Event, EventBus from ..core.profile import Profile -from ..anoncreds.issuer import AnonCredsIssuerError +from ..anoncreds.anoncreds.issuer import AnonCredsIssuerError from ..ledger.base import BaseLedger from ..ledger.multiple_ledger.base_manager import BaseMultipleLedgerManager from ..ledger.error import LedgerError diff --git a/aries_cloudagent/revocation/tests/test_manager.py b/aries_cloudagent/revocation/tests/test_manager.py index 5fe2c86bc7..3cf5ae1bec 100644 --- a/aries_cloudagent/revocation/tests/test_manager.py +++ b/aries_cloudagent/revocation/tests/test_manager.py @@ -8,7 +8,7 @@ ) from ...core.in_memory import InMemoryProfile -from ...anoncreds.issuer import AnonCredsIssuer +from ...anoncreds.anoncreds.issuer import AnonCredsIssuer from ...protocols.issue_credential.v1_0.models.credential_exchange import ( V10CredentialExchange, ) From 95aa5fa62ce5d051f6a1b713caf9ff6d2c9336e6 Mon Sep 17 00:00:00 2001 From: Adam Burdett Date: Thu, 30 Mar 2023 11:51:54 -0600 Subject: [PATCH 093/150] platform in docker compose file Signed-off-by: Adam Burdett --- docker-compose.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index 0bf845c634..603b37d814 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,7 @@ version: '3' services: alice: + platform: linux/amd64 image: acapy-run build: context: . @@ -27,6 +28,7 @@ services: retries: 5 bob: + platform: linux/amd64 image: acapy-run ports: - 3005:3005 @@ -51,6 +53,7 @@ services: tests: + platform: linux/amd64 image: anoncreds-test build: context: . From 63dc426d12198df5d534225008c59cb1105d2b3a Mon Sep 17 00:00:00 2001 From: Char Howland Date: Thu, 30 Mar 2023 14:00:32 -0600 Subject: [PATCH 094/150] fix: circular dependency issues Signed-off-by: Char Howland --- .../anoncreds/default/legacy_indy/registry.py | 2 +- .../anoncreds/anoncreds/holder.py | 17 ++- .../anoncreds/anoncreds/issuer.py | 6 +- .../anoncreds/anoncreds/routes.py | 8 +- aries_cloudagent/askar/profile.py | 27 ---- aries_cloudagent/holder/routes.py | 2 +- aries_cloudagent/ledger/base.py | 119 ------------------ .../credential_definitions/routes.py | 2 +- aries_cloudagent/messaging/schemas/routes.py | 2 +- .../issue_credential/v1_0/manager.py | 4 +- .../v2_0/formats/indy/handler.py | 9 +- .../present_proof/indy/pres_exch_handler.py | 2 +- .../handlers/presentation_request_handler.py | 2 +- .../v2_0/formats/indy/handler.py | 8 +- .../protocols/present_proof/v2_0/routes.py | 2 +- aries_cloudagent/revocation/manager.py | 4 +- .../models/issuer_rev_reg_record.py | 3 +- 17 files changed, 37 insertions(+), 182 deletions(-) diff --git a/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy/registry.py b/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy/registry.py index b766e9d22f..e239cef014 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy/registry.py +++ b/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy/registry.py @@ -257,7 +257,7 @@ async def register_credential_definition( raise AnonCredsRegistrationError(reason) # Check if in wallet but not on ledger - issuer = profile.inject(AnonCredsIssuer) + issuer = AnonCredsIssuer(profile) if await issuer.credential_definition_in_wallet(cred_def_id): try: await self.get_credential_definition(profile, cred_def_id) diff --git a/aries_cloudagent/anoncreds/anoncreds/holder.py b/aries_cloudagent/anoncreds/anoncreds/holder.py index 3e3cace0dc..db20770b4f 100644 --- a/aries_cloudagent/anoncreds/anoncreds/holder.py +++ b/aries_cloudagent/anoncreds/anoncreds/holder.py @@ -5,10 +5,8 @@ import logging import re import uuid - from typing import Dict, Optional, Sequence, Tuple, Union -from aries_askar import AskarError, AskarErrorCode from anoncreds import ( AnoncredsError, Credential, @@ -18,12 +16,13 @@ Presentation, PresentCredentials, ) -from ...core.error import BaseError - -from ..models.anoncreds_cred_def import CredDef +from aries_askar import AskarError, AskarErrorCode +from ...askar.profile import AskarProfile +from ...core.error import BaseError +from ...ledger.base import BaseLedger from ...wallet.error import WalletNotFoundError - +from ..models.anoncreds_cred_def import CredDef LOGGER = logging.getLogger(__name__) @@ -57,8 +56,7 @@ class AnonCredsHolder: MASTER_SECRET_ID = "default" - def __init__(self, profile): - # TODO: fix circular dependency issue with AskarProfile preventing type hinting + def __init__(self, profile: AskarProfile): """ Initialize an AnonCredsHolder instance. @@ -398,9 +396,8 @@ async def _get_credential(self, credential_id: str) -> Credential: raise AnonCredsHolderError("Error loading requested credential") from err async def credential_revoked( - self, ledger, credential_id: str, fro: int = None, to: int = None + self, ledger: BaseLedger, credential_id: str, fro: int = None, to: int = None ) -> bool: - # TODO: fix circular dependency issue with BaseLedger preventing type hinting """ Check ledger for revocation status of credential by cred id. diff --git a/aries_cloudagent/anoncreds/anoncreds/issuer.py b/aries_cloudagent/anoncreds/anoncreds/issuer.py index de5acf6542..ad5110eafe 100644 --- a/aries_cloudagent/anoncreds/anoncreds/issuer.py +++ b/aries_cloudagent/anoncreds/anoncreds/issuer.py @@ -18,6 +18,7 @@ ) from aries_askar import AskarError +from ...askar.profile import AskarProfile from ...core.error import BaseError from ..models.anoncreds_cred_def import CredDef, CredDefResult, CredDefState from ..models.anoncreds_revocation import ( @@ -56,8 +57,7 @@ class AnonCredsIssuerRevocationRegistryFullError(AnonCredsIssuerError): class AnonCredsIssuer: """AnonCreds issuer class.""" - def __init__(self, profile): - # TODO: fix circular dependency issue with AskarProfile preventing type hinting + def __init__(self, profile: AskarProfile): """ Initialize an IndyCredxIssuer instance. @@ -68,7 +68,7 @@ def __init__(self, profile): self._profile = profile @property - def profile(self): + def profile(self) -> AskarProfile: """Accessor for the profile instance.""" return self._profile diff --git a/aries_cloudagent/anoncreds/anoncreds/routes.py b/aries_cloudagent/anoncreds/anoncreds/routes.py index 6877b77c8a..cbd79322a7 100644 --- a/aries_cloudagent/anoncreds/anoncreds/routes.py +++ b/aries_cloudagent/anoncreds/anoncreds/routes.py @@ -174,7 +174,7 @@ async def schemas_post(request: web.BaseRequest): name = schema_data.get("name") version = schema_data.get("version") - issuer = context.inject(AnonCredsIssuer) + issuer = AnonCredsIssuer(context.profile) result = await issuer.create_and_register_schema( issuer_id, name, version, attr_names, options=options ) @@ -243,7 +243,7 @@ async def schemas_get(request: web.BaseRequest): schema_name = request.query.get("schema_name") schema_version = request.query.get("schema_version") - issuer = context.inject(AnonCredsIssuer) + issuer = AnonCredsIssuer(context.profile) schema_ids = await issuer.get_created_schemas( schema_name, schema_version, schema_issuer_id ) @@ -269,7 +269,7 @@ async def cred_def_post(request: web.BaseRequest): schema_id = cred_def.get("schemaId") tag = cred_def.get("tag") - issuer = context.inject(AnonCredsIssuer) + issuer = AnonCredsIssuer(context.profile) result = await issuer.create_and_register_credential_definition( issuer_id, schema_id, @@ -322,7 +322,7 @@ async def cred_defs_get(request: web.BaseRequest): """ context: AdminRequestContext = request["context"] - issuer = context.inject(AnonCredsIssuer) + issuer = AnonCredsIssuer(context.profile) cred_def_ids = await issuer.get_created_credential_definitions( issuer_id=request.query.get("issuer_id"), diff --git a/aries_cloudagent/askar/profile.py b/aries_cloudagent/askar/profile.py index c3c9d48dfe..721cdb3d62 100644 --- a/aries_cloudagent/askar/profile.py +++ b/aries_cloudagent/askar/profile.py @@ -16,9 +16,6 @@ from ..config.provider import ClassProvider from ..core.error import ProfileError from ..core.profile import Profile, ProfileManager, ProfileSession -from ..anoncreds.anoncreds.holder import AnonCredsHolder -from ..anoncreds.anoncreds.issuer import AnonCredsIssuer -from ..anoncreds.anoncreds.verifier import AnonCredsVerifier from ..ledger.base import BaseLedger from ..ledger.indy_vdr import IndyVdrLedger, IndyVdrLedgerPool from ..storage.base import BaseStorage, BaseStorageSearch @@ -99,21 +96,6 @@ def bind_providers(self): "aries_cloudagent.storage.askar.AskarStorageSearch", ref(self) ), ) - - injector.bind_provider( - AnonCredsHolder, - ClassProvider( - "aries_cloudagent.anoncreds.anoncreds.holder.AnonCredsHolder", - ref(self), - ), - ) - injector.bind_provider( - AnonCredsIssuer, - ClassProvider( - "aries_cloudagent.anoncreds.anoncreds.issuer.AnonCredsIssuer", - ref(self), - ), - ) injector.bind_provider( VCHolder, ClassProvider( @@ -121,19 +103,10 @@ def bind_providers(self): ref(self), ), ) - if self.ledger_pool: injector.bind_provider( BaseLedger, ClassProvider(IndyVdrLedger, self.ledger_pool, ref(self)) ) - if self.ledger_pool or self.settings.get("ledger.ledger_config_list"): - injector.bind_provider( - AnonCredsVerifier, - ClassProvider( - "aries_cloudagent.anoncreds.anoncreds.verifier.AnonCredsVerifier", - ref(self), - ), - ) def session(self, context: InjectionContext = None) -> "AskarProfileSession": """Start a new interactive session with no transaction support requested.""" diff --git a/aries_cloudagent/holder/routes.py b/aries_cloudagent/holder/routes.py index f1ead02307..3520942a71 100644 --- a/aries_cloudagent/holder/routes.py +++ b/aries_cloudagent/holder/routes.py @@ -175,7 +175,7 @@ async def credentials_get(request: web.BaseRequest): context: AdminRequestContext = request["context"] credential_id = request.match_info["credential_id"] - holder = context.profile.inject(AnonCredsHolder) + holder = AnonCredsHolder(context.profile) try: credential = await holder.get_credential(credential_id) except WalletNotFoundError as err: diff --git a/aries_cloudagent/ledger/base.py b/aries_cloudagent/ledger/base.py index b2cc4a0780..57d301c976 100644 --- a/aries_cloudagent/ledger/base.py +++ b/aries_cloudagent/ledger/base.py @@ -9,11 +9,6 @@ from hashlib import sha256 from typing import List, Optional, Sequence, Tuple, Union -from ..anoncreds.anoncreds.issuer import ( - DEFAULT_CRED_DEF_TAG, - AnonCredsIssuer, - AnonCredsIssuerError, -) from ..utils import sentinel from ..wallet.did_info import DIDInfo @@ -388,120 +383,6 @@ async def send_revoc_reg_entry( ) -> dict: """Publish a revocation registry entry to the ledger.""" - async def create_and_send_credential_definition( - self, - issuer: AnonCredsIssuer, - schema_id: str, - signature_type: str = None, - tag: str = None, - support_revocation: bool = False, - write_ledger: bool = True, - endorser_did: str = None, - ) -> Tuple[str, dict, bool]: - """ - Send credential definition to ledger and store relevant key matter in wallet. - - Args: - issuer: The issuer instance to use for credential definition creation - schema_id: The schema id of the schema to create cred def for - signature_type: The signature type to use on the credential definition - tag: Optional tag to distinguish multiple credential definitions - support_revocation: Optional flag to enable revocation for this cred def - - Returns: - Tuple with cred def id, cred def structure, and whether it's novel - - """ - public_info = await self.get_wallet_public_did() - if not public_info: - raise BadLedgerRequestError( - "Cannot publish credential definition without a public DID" - ) - - schema = await self.get_schema(schema_id) - if not schema: - raise LedgerError(f"Ledger {self.pool_name} has no schema {schema_id}") - - novel = False - - # check if cred def is on ledger already - for test_tag in [tag] if tag else ["tag", DEFAULT_CRED_DEF_TAG]: - credential_definition_id = issuer.make_credential_definition_id( - public_info.did, schema, signature_type, test_tag - ) - ledger_cred_def = await self.fetch_credential_definition( - credential_definition_id - ) - if ledger_cred_def: - LOGGER.warning( - "Credential definition %s already exists on ledger %s", - credential_definition_id, - self.pool_name, - ) - - try: - if not await issuer.credential_definition_in_wallet( - credential_definition_id - ): - raise LedgerError( - f"Credential definition {credential_definition_id} is on " - f"ledger {self.pool_name} but not in wallet " - f"{self.profile.name}" - ) - except AnonCredsIssuerError as err: - raise LedgerError(err.message) from err - - credential_definition_json = json.dumps(ledger_cred_def) - break - else: # no such cred def on ledger - try: - if await issuer.credential_definition_in_wallet( - credential_definition_id - ): - raise LedgerError( - f"Credential definition {credential_definition_id} is in " - f"wallet {self.profile.name} but not on ledger " - f"{self.pool.name}" - ) - except AnonCredsIssuerError as err: - raise LedgerError(err.message) from err - - # Cred def is neither on ledger nor in wallet: create and send it - novel = True - try: - ( - credential_definition_id, - credential_definition_json, - ) = await issuer.create_and_store_credential_definition( - public_info.did, - schema, - signature_type, - tag, - support_revocation, - ) - except AnonCredsIssuerError as err: - raise LedgerError(err.message) from err - - if await self.is_ledger_read_only(): - raise LedgerError( - "Error cannot write cred def when ledger is in read only mode" - ) - - cred_def_req = await self._create_credential_definition_request( - public_info, - credential_definition_json, - write_ledger=write_ledger, - endorser_did=endorser_did, - ) - - resp = await self.txn_submit( - cred_def_req, True, sign_did=public_info, write_ledger=write_ledger - ) - if not write_ledger: - return (credential_definition_id, {"signed_txn": resp}, novel) - - return (credential_definition_id, json.loads(credential_definition_json), novel) - async def send_credential_definition( self, schema_id: str, diff --git a/aries_cloudagent/messaging/credential_definitions/routes.py b/aries_cloudagent/messaging/credential_definitions/routes.py index b3a4253e9e..9abaabc8c8 100644 --- a/aries_cloudagent/messaging/credential_definitions/routes.py +++ b/aries_cloudagent/messaging/credential_definitions/routes.py @@ -248,7 +248,7 @@ async def credential_definitions_send_credential_definition(request: web.BaseReq reason += ": missing wallet-type?" raise web.HTTPForbidden(reason=reason) - issuer = context.inject(AnonCredsIssuer) + issuer = AnonCredsIssuer(context.profile) try: # even if in wallet, send it and raise if erroneously so async with ledger: (cred_def_id, cred_def, novel) = await shield( diff --git a/aries_cloudagent/messaging/schemas/routes.py b/aries_cloudagent/messaging/schemas/routes.py index 012c53a9ab..9fa3b6ff36 100644 --- a/aries_cloudagent/messaging/schemas/routes.py +++ b/aries_cloudagent/messaging/schemas/routes.py @@ -254,7 +254,7 @@ async def schemas_send_schema(request: web.BaseRequest): reason += ": missing wallet-type?" raise web.HTTPForbidden(reason=reason) - issuer = context.inject(AnonCredsIssuer) + issuer = AnonCredsIssuer(context.profile) async with ledger: try: # if create_transaction_for_endorser, then the returned "schema_def" diff --git a/aries_cloudagent/protocols/issue_credential/v1_0/manager.py b/aries_cloudagent/protocols/issue_credential/v1_0/manager.py index a5a9c5f759..153f3ee304 100644 --- a/aries_cloudagent/protocols/issue_credential/v1_0/manager.py +++ b/aries_cloudagent/protocols/issue_credential/v1_0/manager.py @@ -436,7 +436,7 @@ async def _create(): credential_definition_id ) - holder = self._profile.inject(AnonCredsHolder) + holder = AnonCredsHolder(self._profile) request_json, metadata_json = await holder.create_credential_request( cred_offer_ser, credential_definition, @@ -815,7 +815,7 @@ async def store_credential( raw_cred_serde.de.rev_reg_id ) - holder = self._profile.inject(AnonCredsHolder) + holder = AnonCredsHolder(self._profile) if ( cred_ex_record.credential_proposal_dict and cred_ex_record.credential_proposal_dict.credential_proposal diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/handler.py b/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/handler.py index 21af194883..ab6d46def6 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/handler.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/handler.py @@ -189,7 +189,7 @@ async def create_offer( ) -> CredFormatAttachment: """Create indy credential offer.""" - issuer = self.profile.inject(AnonCredsIssuer) + issuer = AnonCredsIssuer(self.profile) ledger = self.profile.inject(BaseLedger) cache = self.profile.inject_or(BaseCache) @@ -272,7 +272,7 @@ async def _create(): self.profile, cred_def_id ) - holder = self.profile.inject(AnonCredsHolder) + holder = AnonCredsHolder(self.profile) request_json, metadata_json = await holder.create_credential_request( cred_offer, cred_def_result.credential_definition, holder_did ) @@ -330,7 +330,8 @@ async def issue_credential( schema_id = cred_offer["schema_id"] cred_def_id = cred_offer["cred_def_id"] - issuer = self.profile.inject(AnonCredsIssuer) + issuer = AnonCredsIssuer(self.profile) + revocable = await issuer.cred_def_supports_revocation(cred_def_id) result = None @@ -436,7 +437,7 @@ async def store_credential( ) rev_reg_def = rev_reg_def_result.revocation_registry.serialize() - holder = self.profile.inject(AnonCredsHolder) + holder = AnonCredsHolder(self.profile) cred_offer_message = cred_ex_record.cred_offer mime_types = None if cred_offer_message and cred_offer_message.credential_preview: diff --git a/aries_cloudagent/protocols/present_proof/indy/pres_exch_handler.py b/aries_cloudagent/protocols/present_proof/indy/pres_exch_handler.py index 1b39e29446..75f58b0c55 100644 --- a/aries_cloudagent/protocols/present_proof/indy/pres_exch_handler.py +++ b/aries_cloudagent/protocols/present_proof/indy/pres_exch_handler.py @@ -31,7 +31,7 @@ def __init__( """Initialize PresExchange Handler.""" super().__init__() self._profile = profile - self.holder = profile.inject(AnonCredsHolder) + self.holder = AnonCredsHolder(profile) def _extract_proof_request(self, pres_ex_record): if isinstance(pres_ex_record, V20PresExRecord): diff --git a/aries_cloudagent/protocols/present_proof/v1_0/handlers/presentation_request_handler.py b/aries_cloudagent/protocols/present_proof/v1_0/handlers/presentation_request_handler.py index 16ebcac9c5..e60df9e1ec 100644 --- a/aries_cloudagent/protocols/present_proof/v1_0/handlers/presentation_request_handler.py +++ b/aries_cloudagent/protocols/present_proof/v1_0/handlers/presentation_request_handler.py @@ -122,7 +122,7 @@ async def handle(self, context: RequestContext, responder: BaseResponder): req_creds = await indy_proof_req_preview2indy_requested_creds( indy_proof_request, presentation_preview, - holder=context.inject(AnonCredsHolder), + holder=AnonCredsHolder(context.profile), ) except ValueError as err: self._logger.warning(f"{err}") diff --git a/aries_cloudagent/protocols/present_proof/v2_0/formats/indy/handler.py b/aries_cloudagent/protocols/present_proof/v2_0/formats/indy/handler.py index bc85987ec3..c37b1c5f00 100644 --- a/aries_cloudagent/protocols/present_proof/v2_0/formats/indy/handler.py +++ b/aries_cloudagent/protocols/present_proof/v2_0/formats/indy/handler.py @@ -143,7 +143,7 @@ async def create_pres( await indy_proof_req_preview2indy_requested_creds( indy_proof_request, preview=None, - holder=self._profile.inject(AnonCredsHolder), + holder=AnonCredsHolder(self._profile), ) ) except ValueError as err: @@ -320,7 +320,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(IndyPresExchangeHandler.format) indy_proof = pres_ex_record.pres.attachment(IndyPresExchangeHandler.format) - verifier = self._profile.inject(AnonCredsVerifier) + verifier = AnonCredsVerifier(self._profile) + ( schemas, cred_defs, @@ -328,7 +329,8 @@ async def verify_pres(self, pres_ex_record: V20PresExRecord) -> V20PresExRecord: rev_status_lists, ) = await verifier.process_pres_identifiers(indy_proof["identifiers"]) - verifier = self._profile.inject(AnonCredsVerifier) + verifier = AnonCredsVerifier(self._profile) + (verified, verified_msgs) = await verifier.verify_presentation( indy_proof_request, indy_proof, diff --git a/aries_cloudagent/protocols/present_proof/v2_0/routes.py b/aries_cloudagent/protocols/present_proof/v2_0/routes.py index 921cf310ba..aa7fe17e0a 100644 --- a/aries_cloudagent/protocols/present_proof/v2_0/routes.py +++ b/aries_cloudagent/protocols/present_proof/v2_0/routes.py @@ -479,7 +479,7 @@ async def present_proof_credentials_list(request: web.BaseRequest): start = int(start) if isinstance(start, str) else 0 count = int(count) if isinstance(count, str) else 10 - indy_holder = profile.inject(AnonCredsHolder) + indy_holder = AnonCredsHolder(profile) indy_credentials = [] # INDY try: diff --git a/aries_cloudagent/revocation/manager.py b/aries_cloudagent/revocation/manager.py index 834fccb565..8494d02e0e 100644 --- a/aries_cloudagent/revocation/manager.py +++ b/aries_cloudagent/revocation/manager.py @@ -107,7 +107,7 @@ async def revoke_credential( along with any revocations pending against it """ - issuer = self._profile.inject(AnonCredsIssuer) + issuer = AnonCredsIssuer(self._profile) revoc = IndyRevocation(self._profile) issuer_rr_rec = await revoc.get_issuer_rev_reg_record(rev_reg_id) @@ -208,7 +208,7 @@ async def publish_pending_revocations( Returns: mapping from each revocation registry id to its cred rev ids published. """ result = {} - issuer = self._profile.inject(AnonCredsIssuer) + issuer = AnonCredsIssuer(self._profile) async with self._profile.session() as session: issuer_rr_recs = await IssuerRevRegRecord.query_by_pending(session) diff --git a/aries_cloudagent/revocation/models/issuer_rev_reg_record.py b/aries_cloudagent/revocation/models/issuer_rev_reg_record.py index 79780847b9..50b79e44c4 100644 --- a/aries_cloudagent/revocation/models/issuer_rev_reg_record.py +++ b/aries_cloudagent/revocation/models/issuer_rev_reg_record.py @@ -181,7 +181,8 @@ async def generate_registry(self, profile: Profile): ) ) - issuer = profile.inject(AnonCredsIssuer) + issuer = AnonCredsIssuer(profile) + tails_hopper_dir = indy_client_dir(join("tails", ".hopper"), create=True) LOGGER.debug("Creating revocation registry with size: %d", self.max_cred_num) From a21d73afae7a2c19c7c633ba8cf4095886e26170 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Thu, 30 Mar 2023 17:12:24 -0600 Subject: [PATCH 095/150] fix: remove inner anoncreds directory Signed-off-by: Char Howland --- aries_cloudagent/anoncreds/__init__.py | 42 +++++++++++++++++ .../anoncreds/anoncreds/__init__.py | 45 ------------------- .../anoncreds/anoncreds/tests/__init__.py | 0 .../anoncreds/{anoncreds => }/base.py | 12 ++--- .../default/did_indy/__init__.py | 0 .../default/did_indy/registry.py | 10 ++--- .../default/did_indy/routes.py | 0 .../default/did_web/__init__.py | 0 .../default/did_web/registry.py | 10 ++--- .../{anoncreds => }/default/did_web/routes.py | 0 .../default/legacy_indy/__init__.py | 0 .../default/legacy_indy/registry.py | 26 +++++------ .../default/legacy_indy/routes.py | 0 .../anoncreds/{anoncreds => }/holder.py | 10 ++--- .../anoncreds/{anoncreds => }/issuer.py | 10 ++--- .../anoncreds/models/anoncreds_cred_def.py | 22 ++++++++- aries_cloudagent/anoncreds/models/xform.py | 2 +- .../anoncreds/{anoncreds => }/registry.py | 8 ++-- .../anoncreds/{anoncreds => }/routes.py | 10 ++--- .../tests/test_cred_issuance.py | 12 ++--- .../{anoncreds => }/tests/test_routes.py | 0 .../anoncreds/{anoncreds => }/verifier.py | 10 ++--- aries_cloudagent/config/default_context.py | 14 +++--- aries_cloudagent/core/conductor.py | 2 +- aries_cloudagent/holder/routes.py | 2 +- aries_cloudagent/holder/tests/test_routes.py | 2 +- aries_cloudagent/ledger/tests/test_indy.py | 2 +- .../ledger/tests/test_indy_vdr.py | 2 +- .../credential_definitions/routes.py | 2 +- .../tests/test_routes.py | 2 +- aries_cloudagent/messaging/schemas/routes.py | 2 +- .../messaging/schemas/tests/test_routes.py | 2 +- .../endorse_transaction/v1_0/manager.py | 2 +- .../endorse_transaction/v1_0/routes.py | 2 +- .../v1_0/handlers/credential_issue_handler.py | 2 +- .../v1_0/handlers/credential_offer_handler.py | 2 +- .../handlers/credential_proposal_handler.py | 2 +- .../handlers/credential_request_handler.py | 2 +- .../issue_credential/v1_0/manager.py | 4 +- .../protocols/issue_credential/v1_0/routes.py | 4 +- .../v1_0/tests/test_manager.py | 4 +- .../v2_0/formats/indy/handler.py | 6 +-- .../v2_0/formats/indy/tests/test_handler.py | 4 +- .../v2_0/handlers/cred_issue_handler.py | 2 +- .../v2_0/handlers/cred_offer_handler.py | 2 +- .../v2_0/handlers/cred_proposal_handler.py | 2 +- .../v2_0/handlers/cred_request_handler.py | 2 +- .../protocols/issue_credential/v2_0/routes.py | 4 +- .../v2_0/tests/test_manager.py | 2 +- .../present_proof/indy/pres_exch_handler.py | 4 +- .../handlers/presentation_request_handler.py | 2 +- .../test_presentation_request_handler.py | 2 +- .../protocols/present_proof/v1_0/manager.py | 2 +- .../protocols/present_proof/v1_0/routes.py | 2 +- .../present_proof/v1_0/tests/test_manager.py | 6 +-- .../present_proof/v1_0/tests/test_routes.py | 4 +- .../v2_0/formats/indy/handler.py | 4 +- .../v2_0/handlers/pres_request_handler.py | 2 +- .../tests/test_pres_request_handler.py | 2 +- .../protocols/present_proof/v2_0/routes.py | 2 +- .../present_proof/v2_0/tests/test_manager.py | 4 +- .../present_proof/v2_0/tests/test_routes.py | 4 +- aries_cloudagent/revocation/manager.py | 2 +- .../models/issuer_rev_reg_record.py | 2 +- .../tests/test_issuer_rev_reg_record.py | 2 +- aries_cloudagent/revocation/routes.py | 2 +- .../revocation/tests/test_manager.py | 2 +- 67 files changed, 186 insertions(+), 173 deletions(-) delete mode 100644 aries_cloudagent/anoncreds/anoncreds/__init__.py delete mode 100644 aries_cloudagent/anoncreds/anoncreds/tests/__init__.py rename aries_cloudagent/anoncreds/{anoncreds => }/base.py (93%) rename aries_cloudagent/anoncreds/{anoncreds => }/default/did_indy/__init__.py (100%) rename aries_cloudagent/anoncreds/{anoncreds => }/default/did_indy/registry.py (92%) rename aries_cloudagent/anoncreds/{anoncreds => }/default/did_indy/routes.py (100%) rename aries_cloudagent/anoncreds/{anoncreds => }/default/did_web/__init__.py (100%) rename aries_cloudagent/anoncreds/{anoncreds => }/default/did_web/registry.py (91%) rename aries_cloudagent/anoncreds/{anoncreds => }/default/did_web/routes.py (100%) rename aries_cloudagent/anoncreds/{anoncreds => }/default/legacy_indy/__init__.py (100%) rename aries_cloudagent/anoncreds/{anoncreds => }/default/legacy_indy/registry.py (95%) rename aries_cloudagent/anoncreds/{anoncreds => }/default/legacy_indy/routes.py (100%) rename aries_cloudagent/anoncreds/{anoncreds => }/holder.py (98%) rename aries_cloudagent/anoncreds/{anoncreds => }/issuer.py (99%) rename aries_cloudagent/anoncreds/{anoncreds => }/registry.py (96%) rename aries_cloudagent/anoncreds/{anoncreds => }/routes.py (98%) rename aries_cloudagent/anoncreds/{anoncreds => }/tests/test_cred_issuance.py (97%) rename aries_cloudagent/anoncreds/{anoncreds => }/tests/test_routes.py (100%) rename aries_cloudagent/anoncreds/{anoncreds => }/verifier.py (98%) diff --git a/aries_cloudagent/anoncreds/__init__.py b/aries_cloudagent/anoncreds/__init__.py index e69de29bb2..f18f0bad83 100644 --- a/aries_cloudagent/anoncreds/__init__.py +++ b/aries_cloudagent/anoncreds/__init__.py @@ -0,0 +1,42 @@ +import logging + +from ..config.injection_context import InjectionContext +from ..config.provider import ClassProvider + +from .registry import AnonCredsRegistry + +LOGGER = logging.getLogger(__name__) + + +async def setup(context: InjectionContext): + """Set up default resolvers.""" + registry = context.inject_or(AnonCredsRegistry) + if not registry: + LOGGER.warning("No AnonCredsRegistry instance found in context") + return + + indy_registry = ClassProvider( + "aries_cloudagent.anoncreds.default.did_indy.registry.DIDIndyRegistry", + # supported_identifiers=[], + # method_name="did:indy", + ).provide(context.settings, context.injector) + await indy_registry.setup(context) + registry.register(indy_registry) + + web_registry = ClassProvider( + "aries_cloudagent.anoncreds.default.did_web.registry.DIDWebRegistry", + # supported_identifiers=[], + # method_name="did:web", + ).provide(context.settings, context.injector) + await web_registry.setup(context) + registry.register(web_registry) + + legacy_indy_registry = ClassProvider( + "aries_cloudagent.anoncreds.default.legacy_indy.registry.LegacyIndyRegistry", + # supported_identifiers=[], + # method_name="", + ).provide(context.settings, context.injector) + await legacy_indy_registry.setup(context) + registry.register(legacy_indy_registry) + + # TODO: add context.settings diff --git a/aries_cloudagent/anoncreds/anoncreds/__init__.py b/aries_cloudagent/anoncreds/anoncreds/__init__.py deleted file mode 100644 index 0490ab538f..0000000000 --- a/aries_cloudagent/anoncreds/anoncreds/__init__.py +++ /dev/null @@ -1,45 +0,0 @@ -import logging - -from ...config.injection_context import InjectionContext -from ...config.provider import ClassProvider - -from .registry import AnonCredsRegistry - -LOGGER = logging.getLogger(__name__) - - -async def setup(context: InjectionContext): - """Set up default resolvers.""" - registry = context.inject_or(AnonCredsRegistry) - if not registry: - LOGGER.warning("No AnonCredsRegistry instance found in context") - return - - indy_registry = ClassProvider( - "aries_cloudagent.anoncreds.anoncreds.default.did_indy.registry" - ".DIDIndyRegistry", - # supported_identifiers=[], - # method_name="did:indy", - ).provide(context.settings, context.injector) - await indy_registry.setup(context) - registry.register(indy_registry) - - web_registry = ClassProvider( - "aries_cloudagent.anoncreds.anoncreds.default.did_web.registry" - ".DIDWebRegistry", - # supported_identifiers=[], - # method_name="did:web", - ).provide(context.settings, context.injector) - await web_registry.setup(context) - registry.register(web_registry) - - legacy_indy_registry = ClassProvider( - "aries_cloudagent.anoncreds.anoncreds.default.legacy_indy.registry" - ".LegacyIndyRegistry", - # supported_identifiers=[], - # method_name="", - ).provide(context.settings, context.injector) - await legacy_indy_registry.setup(context) - registry.register(legacy_indy_registry) - - # TODO: add context.settings diff --git a/aries_cloudagent/anoncreds/anoncreds/tests/__init__.py b/aries_cloudagent/anoncreds/anoncreds/tests/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/aries_cloudagent/anoncreds/anoncreds/base.py b/aries_cloudagent/anoncreds/base.py similarity index 93% rename from aries_cloudagent/anoncreds/anoncreds/base.py rename to aries_cloudagent/anoncreds/base.py index 1baba21a8f..11ed0042b7 100644 --- a/aries_cloudagent/anoncreds/anoncreds/base.py +++ b/aries_cloudagent/anoncreds/base.py @@ -2,15 +2,15 @@ from abc import ABC, abstractmethod from typing import Generic, Optional, Pattern, Tuple, TypeVar -from ...config.injection_context import InjectionContext -from ...core.error import BaseError -from ...core.profile import Profile -from ..models.anoncreds_cred_def import ( +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.anoncreds_revocation import ( GetRevStatusListResult, AnonCredsRegistryGetRevocationRegistryDefinition, RevRegDef, @@ -18,7 +18,7 @@ RevStatusList, RevStatusListResult, ) -from ..models.anoncreds_schema import AnonCredsSchema, GetSchemaResult, SchemaResult +from .models.anoncreds_schema import AnonCredsSchema, GetSchemaResult, SchemaResult T = TypeVar("T") diff --git a/aries_cloudagent/anoncreds/anoncreds/default/did_indy/__init__.py b/aries_cloudagent/anoncreds/default/did_indy/__init__.py similarity index 100% rename from aries_cloudagent/anoncreds/anoncreds/default/did_indy/__init__.py rename to aries_cloudagent/anoncreds/default/did_indy/__init__.py diff --git a/aries_cloudagent/anoncreds/anoncreds/default/did_indy/registry.py b/aries_cloudagent/anoncreds/default/did_indy/registry.py similarity index 92% rename from aries_cloudagent/anoncreds/anoncreds/default/did_indy/registry.py rename to aries_cloudagent/anoncreds/default/did_indy/registry.py index c1cc09ebda..4e771d9685 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/did_indy/registry.py +++ b/aries_cloudagent/anoncreds/default/did_indy/registry.py @@ -3,16 +3,16 @@ import re from typing import Pattern -from .....config.injection_context import InjectionContext -from .....core.profile import Profile -from ....models.anoncreds_cred_def import ( +from ....config.injection_context import InjectionContext +from ....core.profile import Profile +from ...models.anoncreds_cred_def import ( GetCredDefResult, ) -from ....models.anoncreds_revocation import ( +from ...models.anoncreds_revocation import ( GetRevStatusListResult, AnonCredsRegistryGetRevocationRegistryDefinition, ) -from ....models.anoncreds_schema import GetSchemaResult +from ...models.anoncreds_schema import GetSchemaResult from ...base import BaseAnonCredsRegistrar, BaseAnonCredsResolver LOGGER = logging.getLogger(__name__) diff --git a/aries_cloudagent/anoncreds/anoncreds/default/did_indy/routes.py b/aries_cloudagent/anoncreds/default/did_indy/routes.py similarity index 100% rename from aries_cloudagent/anoncreds/anoncreds/default/did_indy/routes.py rename to aries_cloudagent/anoncreds/default/did_indy/routes.py diff --git a/aries_cloudagent/anoncreds/anoncreds/default/did_web/__init__.py b/aries_cloudagent/anoncreds/default/did_web/__init__.py similarity index 100% rename from aries_cloudagent/anoncreds/anoncreds/default/did_web/__init__.py rename to aries_cloudagent/anoncreds/default/did_web/__init__.py diff --git a/aries_cloudagent/anoncreds/anoncreds/default/did_web/registry.py b/aries_cloudagent/anoncreds/default/did_web/registry.py similarity index 91% rename from aries_cloudagent/anoncreds/anoncreds/default/did_web/registry.py rename to aries_cloudagent/anoncreds/default/did_web/registry.py index 2fff2c4a06..af2089c428 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/did_web/registry.py +++ b/aries_cloudagent/anoncreds/default/did_web/registry.py @@ -2,16 +2,16 @@ import re from typing import Pattern -from .....config.injection_context import InjectionContext -from .....core.profile import Profile -from ....models.anoncreds_cred_def import ( +from ....config.injection_context import InjectionContext +from ....core.profile import Profile +from ...models.anoncreds_cred_def import ( GetCredDefResult, ) -from ....models.anoncreds_revocation import ( +from ...models.anoncreds_revocation import ( GetRevStatusListResult, AnonCredsRegistryGetRevocationRegistryDefinition, ) -from ....models.anoncreds_schema import GetSchemaResult +from ...models.anoncreds_schema import GetSchemaResult from ...base import BaseAnonCredsRegistrar, BaseAnonCredsResolver diff --git a/aries_cloudagent/anoncreds/anoncreds/default/did_web/routes.py b/aries_cloudagent/anoncreds/default/did_web/routes.py similarity index 100% rename from aries_cloudagent/anoncreds/anoncreds/default/did_web/routes.py rename to aries_cloudagent/anoncreds/default/did_web/routes.py diff --git a/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy/__init__.py b/aries_cloudagent/anoncreds/default/legacy_indy/__init__.py similarity index 100% rename from aries_cloudagent/anoncreds/anoncreds/default/legacy_indy/__init__.py rename to aries_cloudagent/anoncreds/default/legacy_indy/__init__.py diff --git a/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy/registry.py b/aries_cloudagent/anoncreds/default/legacy_indy/registry.py similarity index 95% rename from aries_cloudagent/anoncreds/anoncreds/default/legacy_indy/registry.py rename to aries_cloudagent/anoncreds/default/legacy_indy/registry.py index e239cef014..0a02da788f 100644 --- a/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy/registry.py +++ b/aries_cloudagent/anoncreds/default/legacy_indy/registry.py @@ -4,32 +4,32 @@ from asyncio import shield from typing import Optional, Pattern -from .....config.injection_context import InjectionContext -from .....core.profile import Profile -from .....ledger.base import BaseLedger -from .....ledger.error import LedgerError, LedgerObjectAlreadyExistsError -from .....ledger.merkel_validation.constants import GET_SCHEMA -from .....ledger.multiple_ledger.ledger_requests_executor import ( +from ....config.injection_context import InjectionContext +from ....core.profile import Profile +from ....ledger.base import BaseLedger +from ....ledger.error import LedgerError, LedgerObjectAlreadyExistsError +from ....ledger.merkel_validation.constants import GET_SCHEMA +from ....ledger.multiple_ledger.ledger_requests_executor import ( GET_CRED_DEF, IndyLedgerRequestsExecutor, ) -from .....multitenant.base import BaseMultitenantManager -from .....revocation.error import RevocationError -from .....revocation.indy import IndyRevocation -from .....storage.error import StorageNotFoundError +from ....multitenant.base import BaseMultitenantManager +from ....revocation.error import RevocationError +from ....revocation.indy import IndyRevocation +from ....storage.error import StorageNotFoundError from ...issuer import AnonCredsIssuer, AnonCredsIssuerError -from ....models.anoncreds_cred_def import ( +from ...models.anoncreds_cred_def import ( CredDef, CredDefState, CredDefValue, CredDefResult, GetCredDefResult, ) -from ....models.anoncreds_revocation import ( +from ...models.anoncreds_revocation import ( GetRevStatusListResult, AnonCredsRegistryGetRevocationRegistryDefinition, ) -from ....models.anoncreds_schema import ( +from ...models.anoncreds_schema import ( GetSchemaResult, AnonCredsSchema, SchemaResult, diff --git a/aries_cloudagent/anoncreds/anoncreds/default/legacy_indy/routes.py b/aries_cloudagent/anoncreds/default/legacy_indy/routes.py similarity index 100% rename from aries_cloudagent/anoncreds/anoncreds/default/legacy_indy/routes.py rename to aries_cloudagent/anoncreds/default/legacy_indy/routes.py diff --git a/aries_cloudagent/anoncreds/anoncreds/holder.py b/aries_cloudagent/anoncreds/holder.py similarity index 98% rename from aries_cloudagent/anoncreds/anoncreds/holder.py rename to aries_cloudagent/anoncreds/holder.py index db20770b4f..c619849dc0 100644 --- a/aries_cloudagent/anoncreds/anoncreds/holder.py +++ b/aries_cloudagent/anoncreds/holder.py @@ -18,11 +18,11 @@ ) from aries_askar import AskarError, AskarErrorCode -from ...askar.profile import AskarProfile -from ...core.error import BaseError -from ...ledger.base import BaseLedger -from ...wallet.error import WalletNotFoundError -from ..models.anoncreds_cred_def import CredDef +from ..askar.profile import AskarProfile +from ..core.error import BaseError +from ..ledger.base import BaseLedger +from ..wallet.error import WalletNotFoundError +from .models.anoncreds_cred_def import CredDef LOGGER = logging.getLogger(__name__) diff --git a/aries_cloudagent/anoncreds/anoncreds/issuer.py b/aries_cloudagent/anoncreds/issuer.py similarity index 99% rename from aries_cloudagent/anoncreds/anoncreds/issuer.py rename to aries_cloudagent/anoncreds/issuer.py index ad5110eafe..32ae9ab263 100644 --- a/aries_cloudagent/anoncreds/anoncreds/issuer.py +++ b/aries_cloudagent/anoncreds/issuer.py @@ -18,16 +18,16 @@ ) from aries_askar import AskarError -from ...askar.profile import AskarProfile -from ...core.error import BaseError -from ..models.anoncreds_cred_def import CredDef, CredDefResult, CredDefState -from ..models.anoncreds_revocation import ( +from ..askar.profile import AskarProfile +from ..core.error import BaseError +from .models.anoncreds_cred_def import CredDef, CredDefResult, CredDefState +from .models.anoncreds_revocation import ( RevRegDef, RevRegDefResult, RevRegDefState, RevStatusList, ) -from ..models.anoncreds_schema import AnonCredsSchema, SchemaResult, SchemaState +from .models.anoncreds_schema import AnonCredsSchema, SchemaResult, SchemaState from .base import AnonCredsSchemaAlreadyExists from .registry import AnonCredsRegistry diff --git a/aries_cloudagent/anoncreds/models/anoncreds_cred_def.py b/aries_cloudagent/anoncreds/models/anoncreds_cred_def.py index 7385b6d6f7..51b0e55fdc 100644 --- a/aries_cloudagent/anoncreds/models/anoncreds_cred_def.py +++ b/aries_cloudagent/anoncreds/models/anoncreds_cred_def.py @@ -4,10 +4,28 @@ from anoncreds import CredentialDefinition from marshmallow import EXCLUDE, fields -from marshmallow.validate import OneOf +from marshmallow.validate import OneOf, Regexp from ...messaging.models.base import BaseModel, BaseModelSchema -from ...messaging.valid import NUM_STR_WHOLE + + +# TODO: remove after solving circular dependency issue +class NumericStrWhole(Regexp): + """Validate value against whole number numeric string.""" + + EXAMPLE = "0" + PATTERN = r"^[0-9]*$" + + def __init__(self): + """Initializer.""" + + super().__init__( + NumericStrWhole.PATTERN, + error="Value {input} is not a non-negative numeric string", + ) + + +NUM_STR_WHOLE = {"validate": NumericStrWhole(), "example": NumericStrWhole.EXAMPLE} class CredDefValuePrimary(BaseModel): diff --git a/aries_cloudagent/anoncreds/models/xform.py b/aries_cloudagent/anoncreds/models/xform.py index 53c0044dea..f0bd75949a 100644 --- a/aries_cloudagent/anoncreds/models/xform.py +++ b/aries_cloudagent/anoncreds/models/xform.py @@ -1,6 +1,6 @@ """Utilities to deal with indy.""" -from ...anoncreds.anoncreds.holder import AnonCredsHolder +from ..holder import AnonCredsHolder from .pres_preview import IndyPresPreview diff --git a/aries_cloudagent/anoncreds/anoncreds/registry.py b/aries_cloudagent/anoncreds/registry.py similarity index 96% rename from aries_cloudagent/anoncreds/anoncreds/registry.py rename to aries_cloudagent/anoncreds/registry.py index 53c75b3b10..13d980f60b 100644 --- a/aries_cloudagent/anoncreds/anoncreds/registry.py +++ b/aries_cloudagent/anoncreds/registry.py @@ -3,13 +3,13 @@ from typing import List, Optional -from ...core.profile import Profile -from ..models.anoncreds_cred_def import ( +from ..core.profile import Profile +from .models.anoncreds_cred_def import ( CredDef, CredDefResult, GetCredDefResult, ) -from ..models.anoncreds_revocation import ( +from .models.anoncreds_revocation import ( GetRevStatusListResult, AnonCredsRegistryGetRevocationRegistryDefinition, RevRegDef, @@ -17,7 +17,7 @@ RevStatusList, RevStatusListResult, ) -from ..models.anoncreds_schema import AnonCredsSchema, GetSchemaResult, SchemaResult +from .models.anoncreds_schema import AnonCredsSchema, GetSchemaResult, SchemaResult from .base import ( AnonCredsRegistrationError, AnonCredsResolutionError, diff --git a/aries_cloudagent/anoncreds/anoncreds/routes.py b/aries_cloudagent/anoncreds/routes.py similarity index 98% rename from aries_cloudagent/anoncreds/anoncreds/routes.py rename to aries_cloudagent/anoncreds/routes.py index cbd79322a7..b8afedd052 100644 --- a/aries_cloudagent/anoncreds/anoncreds/routes.py +++ b/aries_cloudagent/anoncreds/routes.py @@ -13,19 +13,19 @@ from .registry import AnonCredsRegistry from .issuer import AnonCredsIssuer -from ..models.anoncreds_cred_def import ( +from .models.anoncreds_cred_def import ( CredDefSchema, GetCredDefResultSchema, ) -from ..models.anoncreds_schema import ( +from .models.anoncreds_schema import ( AnonCredsSchemaSchema, SchemaResultSchema, GetSchemaResultSchema, ) -from ...admin.request_context import AdminRequestContext -from ...messaging.models.openapi import OpenAPISchema -from ...messaging.valid import ( +from ..admin.request_context import AdminRequestContext +from ..messaging.models.openapi import OpenAPISchema +from ..messaging.valid import ( UUIDFour, ) diff --git a/aries_cloudagent/anoncreds/anoncreds/tests/test_cred_issuance.py b/aries_cloudagent/anoncreds/tests/test_cred_issuance.py similarity index 97% rename from aries_cloudagent/anoncreds/anoncreds/tests/test_cred_issuance.py rename to aries_cloudagent/anoncreds/tests/test_cred_issuance.py index fa2fded348..5c0ca4e31f 100644 --- a/aries_cloudagent/anoncreds/anoncreds/tests/test_cred_issuance.py +++ b/aries_cloudagent/anoncreds/tests/test_cred_issuance.py @@ -4,14 +4,16 @@ from asynctest import mock as async_mock, TestCase as AsyncTestCase -from ....askar.profile import AskarProfileManager -from ....config.injection_context import InjectionContext -from ....ledger.base import BaseLedger -from ....ledger.multiple_ledger.ledger_requests_executor import ( +from .. import holder, issuer + +from ...askar.profile import AskarProfileManager +from ...config.injection_context import InjectionContext +from ...ledger.base import BaseLedger +from ...ledger.multiple_ledger.ledger_requests_executor import ( IndyLedgerRequestsExecutor, ) -from .. import issuer, holder, verifier +from .. import verifier TEST_DID = "55GkHamhTU1ZbTbV2ab9DE" diff --git a/aries_cloudagent/anoncreds/anoncreds/tests/test_routes.py b/aries_cloudagent/anoncreds/tests/test_routes.py similarity index 100% rename from aries_cloudagent/anoncreds/anoncreds/tests/test_routes.py rename to aries_cloudagent/anoncreds/tests/test_routes.py diff --git a/aries_cloudagent/anoncreds/anoncreds/verifier.py b/aries_cloudagent/anoncreds/verifier.py similarity index 98% rename from aries_cloudagent/anoncreds/anoncreds/verifier.py rename to aries_cloudagent/anoncreds/verifier.py index 019c29196f..3299f6846f 100644 --- a/aries_cloudagent/anoncreds/anoncreds/verifier.py +++ b/aries_cloudagent/anoncreds/verifier.py @@ -8,11 +8,11 @@ from anoncreds import AnoncredsError, Presentation -from ...anoncreds.anoncreds.registry import AnonCredsRegistry -from ...anoncreds.models.anoncreds_cred_def import GetCredDefResult -from ...anoncreds.models.xform import indy_proof_req2non_revoc_intervals -from ...core.profile import Profile -from ...messaging.util import canon, encode +from .registry import AnonCredsRegistry +from .models.anoncreds_cred_def import GetCredDefResult +from .models.xform import indy_proof_req2non_revoc_intervals +from ..core.profile import Profile +from ..messaging.util import canon, encode LOGGER = logging.getLogger(__name__) diff --git a/aries_cloudagent/config/default_context.py b/aries_cloudagent/config/default_context.py index a9b6ff29af..8cb951c4d4 100644 --- a/aries_cloudagent/config/default_context.py +++ b/aries_cloudagent/config/default_context.py @@ -1,6 +1,6 @@ """Classes for configuring the default injection context.""" -from ..anoncreds.anoncreds.registry import AnonCredsRegistry +from ..anoncreds.registry import AnonCredsRegistry from ..cache.base import BaseCache from ..cache.in_memory import InMemoryCache from ..core.event_bus import EventBus @@ -129,15 +129,11 @@ async def load_plugins(self, context: InjectionContext): plugin_registry.register_plugin("aries_cloudagent.revocation") plugin_registry.register_plugin("aries_cloudagent.resolver") plugin_registry.register_plugin("aries_cloudagent.wallet") - plugin_registry.register_plugin("aries_cloudagent.anoncreds.anoncreds") + plugin_registry.register_plugin("aries_cloudagent.anoncreds") + plugin_registry.register_plugin("aries_cloudagent.anoncreds.default.did_indy") + plugin_registry.register_plugin("aries_cloudagent.anoncreds.default.did_web") plugin_registry.register_plugin( - "aries_cloudagent.anoncreds.anoncreds.default.did_indy" - ) - plugin_registry.register_plugin( - "aries_cloudagent.anoncreds.anoncreds.default.did_web" - ) - plugin_registry.register_plugin( - "aries_cloudagent.anoncreds.anoncreds.default.legacy_indy" + "aries_cloudagent.anoncreds.default.legacy_indy" ) if context.settings.get("multitenant.admin_enabled"): diff --git a/aries_cloudagent/core/conductor.py b/aries_cloudagent/core/conductor.py index 96cb11a45e..6699f7a38e 100644 --- a/aries_cloudagent/core/conductor.py +++ b/aries_cloudagent/core/conductor.py @@ -27,7 +27,7 @@ from ..config.provider import ClassProvider from ..config.wallet import wallet_config from ..core.profile import Profile -from ..anoncreds.anoncreds.verifier import AnonCredsVerifier +from ..anoncreds.verifier import AnonCredsVerifier from ..ledger.error import LedgerConfigError, LedgerTransactionError from ..ledger.multiple_ledger.base_manager import ( diff --git a/aries_cloudagent/holder/routes.py b/aries_cloudagent/holder/routes.py index 3520942a71..6d9e66b0d3 100644 --- a/aries_cloudagent/holder/routes.py +++ b/aries_cloudagent/holder/routes.py @@ -13,7 +13,7 @@ from marshmallow import fields from ..admin.request_context import AdminRequestContext -from ..anoncreds.anoncreds.holder import AnonCredsHolder, AnonCredsHolderError +from ..anoncreds.holder import AnonCredsHolder, AnonCredsHolderError from ..anoncreds.models.cred_precis import IndyCredInfoSchema from ..ledger.base import BaseLedger from ..ledger.error import LedgerError diff --git a/aries_cloudagent/holder/tests/test_routes.py b/aries_cloudagent/holder/tests/test_routes.py index 49ecca6cf6..4150a81636 100644 --- a/aries_cloudagent/holder/tests/test_routes.py +++ b/aries_cloudagent/holder/tests/test_routes.py @@ -8,7 +8,7 @@ from ...wallet.base import BaseWallet from ...admin.request_context import AdminRequestContext -from ...anoncreds.anoncreds.holder import AnonCredsHolder +from ...anoncreds.holder import AnonCredsHolder from ...ledger.base import BaseLedger from ...storage.vc_holder.base import VCHolder from ...storage.vc_holder.vc_record import VCRecord diff --git a/aries_cloudagent/ledger/tests/test_indy.py b/aries_cloudagent/ledger/tests/test_indy.py index d2f122e43f..61c5d27b87 100644 --- a/aries_cloudagent/ledger/tests/test_indy.py +++ b/aries_cloudagent/ledger/tests/test_indy.py @@ -9,7 +9,7 @@ from ...config.injection_context import InjectionContext from ...cache.in_memory import InMemoryCache -from ...anoncreds.anoncreds.issuer import AnonCredsIssuer, AnonCredsIssuerError +from ...anoncreds.issuer import AnonCredsIssuer, AnonCredsIssuerError from ...anoncreds.sdk.profile import IndySdkProfile from ...storage.record import StorageRecord from ...wallet.base import BaseWallet diff --git a/aries_cloudagent/ledger/tests/test_indy_vdr.py b/aries_cloudagent/ledger/tests/test_indy_vdr.py index 02fc023dfe..484ffbe297 100644 --- a/aries_cloudagent/ledger/tests/test_indy_vdr.py +++ b/aries_cloudagent/ledger/tests/test_indy_vdr.py @@ -7,7 +7,7 @@ import indy_vdr from ...core.in_memory import InMemoryProfile -from ...anoncreds.anoncreds.issuer import AnonCredsIssuer +from ...anoncreds.issuer import AnonCredsIssuer from ...wallet.base import BaseWallet from ...wallet.key_type import KeyType, ED25519 from ...wallet.did_method import SOV, DIDMethods diff --git a/aries_cloudagent/messaging/credential_definitions/routes.py b/aries_cloudagent/messaging/credential_definitions/routes.py index 9abaabc8c8..239da9f19f 100644 --- a/aries_cloudagent/messaging/credential_definitions/routes.py +++ b/aries_cloudagent/messaging/credential_definitions/routes.py @@ -20,7 +20,7 @@ from ...admin.request_context import AdminRequestContext from ...core.event_bus import Event, EventBus from ...core.profile import Profile -from ...anoncreds.anoncreds.issuer import AnonCredsIssuer, AnonCredsIssuerError +from ...anoncreds.issuer import AnonCredsIssuer, AnonCredsIssuerError from ...anoncreds.models.cred_def import CredentialDefinitionSchema from ...ledger.base import BaseLedger from ...ledger.error import LedgerError diff --git a/aries_cloudagent/messaging/credential_definitions/tests/test_routes.py b/aries_cloudagent/messaging/credential_definitions/tests/test_routes.py index a5b14a6f1f..75bfc15534 100644 --- a/aries_cloudagent/messaging/credential_definitions/tests/test_routes.py +++ b/aries_cloudagent/messaging/credential_definitions/tests/test_routes.py @@ -3,7 +3,7 @@ from ....admin.request_context import AdminRequestContext from ....core.in_memory import InMemoryProfile -from ....anoncreds.anoncreds.issuer import AnonCredsIssuer +from ....anoncreds.issuer import AnonCredsIssuer from ....ledger.base import BaseLedger from ....ledger.multiple_ledger.ledger_requests_executor import ( IndyLedgerRequestsExecutor, diff --git a/aries_cloudagent/messaging/schemas/routes.py b/aries_cloudagent/messaging/schemas/routes.py index 9fa3b6ff36..e824cc484b 100644 --- a/aries_cloudagent/messaging/schemas/routes.py +++ b/aries_cloudagent/messaging/schemas/routes.py @@ -16,7 +16,7 @@ from marshmallow.validate import Regexp from ...admin.request_context import AdminRequestContext -from ...anoncreds.anoncreds.issuer import AnonCredsIssuer, AnonCredsIssuerError +from ...anoncreds.issuer import AnonCredsIssuer, AnonCredsIssuerError from ...connections.models.conn_record import ConnRecord from ...core.event_bus import Event, EventBus from ...core.profile import Profile diff --git a/aries_cloudagent/messaging/schemas/tests/test_routes.py b/aries_cloudagent/messaging/schemas/tests/test_routes.py index 62731b672e..568cc96c6a 100644 --- a/aries_cloudagent/messaging/schemas/tests/test_routes.py +++ b/aries_cloudagent/messaging/schemas/tests/test_routes.py @@ -3,7 +3,7 @@ from ....admin.request_context import AdminRequestContext from ....core.in_memory import InMemoryProfile -from ....anoncreds.anoncreds.issuer import AnonCredsIssuer +from ....anoncreds.issuer import AnonCredsIssuer from ....ledger.base import BaseLedger from ....ledger.multiple_ledger.ledger_requests_executor import ( IndyLedgerRequestsExecutor, diff --git a/aries_cloudagent/protocols/endorse_transaction/v1_0/manager.py b/aries_cloudagent/protocols/endorse_transaction/v1_0/manager.py index 6eefdcb2fc..1b4cd172f8 100644 --- a/aries_cloudagent/protocols/endorse_transaction/v1_0/manager.py +++ b/aries_cloudagent/protocols/endorse_transaction/v1_0/manager.py @@ -9,7 +9,7 @@ from ....connections.models.conn_record import ConnRecord from ....core.error import BaseError from ....core.profile import Profile -from ....anoncreds.anoncreds.issuer import AnonCredsIssuerError +from ....anoncreds.issuer import AnonCredsIssuerError from ....ledger.base import BaseLedger from ....ledger.error import LedgerError from ....messaging.credential_definitions.util import notify_cred_def_event diff --git a/aries_cloudagent/protocols/endorse_transaction/v1_0/routes.py b/aries_cloudagent/protocols/endorse_transaction/v1_0/routes.py index 235669b1e8..559cd025c2 100644 --- a/aries_cloudagent/protocols/endorse_transaction/v1_0/routes.py +++ b/aries_cloudagent/protocols/endorse_transaction/v1_0/routes.py @@ -18,7 +18,7 @@ from ....core.event_bus import Event, EventBus from ....core.profile import Profile from ....core.util import STARTUP_EVENT_PATTERN, SHUTDOWN_EVENT_PATTERN -from ....anoncreds.anoncreds.issuer import AnonCredsIssuerError +from ....anoncreds.issuer import AnonCredsIssuerError from ....ledger.error import LedgerError from ....messaging.models.base import BaseModelError from ....messaging.models.openapi import OpenAPISchema diff --git a/aries_cloudagent/protocols/issue_credential/v1_0/handlers/credential_issue_handler.py b/aries_cloudagent/protocols/issue_credential/v1_0/handlers/credential_issue_handler.py index dab8c77b34..2ec672c086 100644 --- a/aries_cloudagent/protocols/issue_credential/v1_0/handlers/credential_issue_handler.py +++ b/aries_cloudagent/protocols/issue_credential/v1_0/handlers/credential_issue_handler.py @@ -1,7 +1,7 @@ """Credential issue message handler.""" from .....core.oob_processor import OobMessageProcessor -from .....anoncreds.anoncreds.holder import AnonCredsHolderError +from .....anoncreds.holder import AnonCredsHolderError from .....messaging.base_handler import BaseHandler, HandlerException from .....messaging.models.base import BaseModelError from .....messaging.request_context import RequestContext diff --git a/aries_cloudagent/protocols/issue_credential/v1_0/handlers/credential_offer_handler.py b/aries_cloudagent/protocols/issue_credential/v1_0/handlers/credential_offer_handler.py index 4f90fa6888..fe5d39b099 100644 --- a/aries_cloudagent/protocols/issue_credential/v1_0/handlers/credential_offer_handler.py +++ b/aries_cloudagent/protocols/issue_credential/v1_0/handlers/credential_offer_handler.py @@ -3,7 +3,7 @@ from .....wallet.util import default_did_from_verkey from .....core.oob_processor import OobMessageProcessor -from .....anoncreds.anoncreds.holder import AnonCredsHolderError +from .....anoncreds.holder import AnonCredsHolderError from .....ledger.error import LedgerError from .....messaging.base_handler import BaseHandler, HandlerException from .....messaging.models.base import BaseModelError diff --git a/aries_cloudagent/protocols/issue_credential/v1_0/handlers/credential_proposal_handler.py b/aries_cloudagent/protocols/issue_credential/v1_0/handlers/credential_proposal_handler.py index c6736479ac..71401296ec 100644 --- a/aries_cloudagent/protocols/issue_credential/v1_0/handlers/credential_proposal_handler.py +++ b/aries_cloudagent/protocols/issue_credential/v1_0/handlers/credential_proposal_handler.py @@ -1,6 +1,6 @@ """Credential proposal message handler.""" -from .....anoncreds.anoncreds.issuer import AnonCredsIssuerError +from .....anoncreds.issuer import AnonCredsIssuerError from .....ledger.error import LedgerError from .....messaging.base_handler import BaseHandler, HandlerException from .....messaging.models.base import BaseModelError diff --git a/aries_cloudagent/protocols/issue_credential/v1_0/handlers/credential_request_handler.py b/aries_cloudagent/protocols/issue_credential/v1_0/handlers/credential_request_handler.py index 0cef4cc937..ff9b363313 100644 --- a/aries_cloudagent/protocols/issue_credential/v1_0/handlers/credential_request_handler.py +++ b/aries_cloudagent/protocols/issue_credential/v1_0/handlers/credential_request_handler.py @@ -1,7 +1,7 @@ """Credential request message handler.""" from .....core.oob_processor import OobMessageProcessor -from .....anoncreds.anoncreds.issuer import AnonCredsIssuerError +from .....anoncreds.issuer import AnonCredsIssuerError from .....ledger.error import LedgerError from .....messaging.base_handler import BaseHandler, HandlerException from .....messaging.models.base import BaseModelError diff --git a/aries_cloudagent/protocols/issue_credential/v1_0/manager.py b/aries_cloudagent/protocols/issue_credential/v1_0/manager.py index 153f3ee304..2396684c47 100644 --- a/aries_cloudagent/protocols/issue_credential/v1_0/manager.py +++ b/aries_cloudagent/protocols/issue_credential/v1_0/manager.py @@ -10,8 +10,8 @@ from ....connections.models.conn_record import ConnRecord from ....core.error import BaseError from ....core.profile import Profile -from ....anoncreds.anoncreds.holder import AnonCredsHolder, AnonCredsHolderError -from ....anoncreds.anoncreds.issuer import ( +from ....anoncreds.holder import AnonCredsHolder, AnonCredsHolderError +from ....anoncreds.issuer import ( AnonCredsIssuer, AnonCredsIssuerRevocationRegistryFullError, ) diff --git a/aries_cloudagent/protocols/issue_credential/v1_0/routes.py b/aries_cloudagent/protocols/issue_credential/v1_0/routes.py index bf5b8947f5..0d385bdf82 100644 --- a/aries_cloudagent/protocols/issue_credential/v1_0/routes.py +++ b/aries_cloudagent/protocols/issue_credential/v1_0/routes.py @@ -16,8 +16,8 @@ from ....admin.request_context import AdminRequestContext from ....connections.models.conn_record import ConnRecord from ....core.profile import Profile -from ....anoncreds.anoncreds.holder import AnonCredsHolderError -from ....anoncreds.anoncreds.issuer import AnonCredsIssuerError +from ....anoncreds.holder import AnonCredsHolderError +from ....anoncreds.issuer import AnonCredsIssuerError from ....ledger.error import LedgerError from ....messaging.credential_definitions.util import CRED_DEF_TAGS from ....messaging.models.base import BaseModelError diff --git a/aries_cloudagent/protocols/issue_credential/v1_0/tests/test_manager.py b/aries_cloudagent/protocols/issue_credential/v1_0/tests/test_manager.py index a0a44c71d9..c9ca5565c3 100644 --- a/aries_cloudagent/protocols/issue_credential/v1_0/tests/test_manager.py +++ b/aries_cloudagent/protocols/issue_credential/v1_0/tests/test_manager.py @@ -10,8 +10,8 @@ from .....core.in_memory import InMemoryProfile from .....cache.base import BaseCache from .....cache.in_memory import InMemoryCache -from .....anoncreds.anoncreds.holder import AnonCredsHolder -from .....anoncreds.anoncreds.issuer import AnonCredsIssuer +from .....anoncreds.holder import AnonCredsHolder +from .....anoncreds.issuer import AnonCredsIssuer from .....messaging.decorators.thread_decorator import ThreadDecorator from .....messaging.credential_definitions.util import CRED_DEF_SENT_RECORD_TYPE from .....messaging.responder import BaseResponder, MockResponder diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/handler.py b/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/handler.py index ab6d46def6..90d656ef2a 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/handler.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/handler.py @@ -7,9 +7,9 @@ from marshmallow import RAISE -from ......anoncreds.anoncreds.registry import AnonCredsRegistry -from ......anoncreds.anoncreds.holder import AnonCredsHolder, AnonCredsHolderError -from ......anoncreds.anoncreds.issuer import ( +from ......anoncreds.registry import AnonCredsRegistry +from ......anoncreds.holder import AnonCredsHolder, AnonCredsHolderError +from ......anoncreds.issuer import ( AnonCredsIssuer, AnonCredsIssuerRevocationRegistryFullError, ) diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/tests/test_handler.py b/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/tests/test_handler.py index 4a6d5be50b..80a98d2c71 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/tests/test_handler.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/tests/test_handler.py @@ -16,14 +16,14 @@ ) from .......multitenant.base import BaseMultitenantManager from .......multitenant.manager import MultitenantManager -from .......anoncreds.anoncreds.issuer import AnonCredsIssuer +from .......anoncreds.issuer import AnonCredsIssuer from .......cache.in_memory import InMemoryCache from .......cache.base import BaseCache from .......storage.record import StorageRecord from .......storage.error import StorageNotFoundError from .......messaging.credential_definitions.util import CRED_DEF_SENT_RECORD_TYPE from .......messaging.decorators.attach_decorator import AttachDecorator -from .......anoncreds.anoncreds.holder import AnonCredsHolder +from .......anoncreds.holder import AnonCredsHolder from ....models.detail.indy import V20CredExRecordIndy from ....messages.cred_proposal import V20CredProposal from ....messages.cred_format import V20CredFormat diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/handlers/cred_issue_handler.py b/aries_cloudagent/protocols/issue_credential/v2_0/handlers/cred_issue_handler.py index 7123f221e5..8e88fa4c9f 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/handlers/cred_issue_handler.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/handlers/cred_issue_handler.py @@ -1,7 +1,7 @@ """Credential issue message handler.""" from .....core.oob_processor import OobMessageProcessor -from .....anoncreds.anoncreds.holder import AnonCredsHolderError +from .....anoncreds.holder import AnonCredsHolderError from .....messaging.base_handler import BaseHandler, HandlerException from .....messaging.models.base import BaseModelError from .....messaging.request_context import RequestContext diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/handlers/cred_offer_handler.py b/aries_cloudagent/protocols/issue_credential/v2_0/handlers/cred_offer_handler.py index 0b432fc16e..1b1e0e532c 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/handlers/cred_offer_handler.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/handlers/cred_offer_handler.py @@ -2,7 +2,7 @@ from .....wallet.util import default_did_from_verkey from .....core.oob_processor import OobMessageProcessor -from .....anoncreds.anoncreds.holder import AnonCredsHolderError +from .....anoncreds.holder import AnonCredsHolderError from .....ledger.error import LedgerError from .....messaging.base_handler import BaseHandler, HandlerException from .....messaging.models.base import BaseModelError diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/handlers/cred_proposal_handler.py b/aries_cloudagent/protocols/issue_credential/v2_0/handlers/cred_proposal_handler.py index 46a86a5f79..4dc7dafdcd 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/handlers/cred_proposal_handler.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/handlers/cred_proposal_handler.py @@ -1,6 +1,6 @@ """Credential proposal message handler.""" -from .....anoncreds.anoncreds.issuer import AnonCredsIssuerError +from .....anoncreds.issuer import AnonCredsIssuerError from .....ledger.error import LedgerError from .....messaging.base_handler import BaseHandler, HandlerException from .....messaging.models.base import BaseModelError diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/handlers/cred_request_handler.py b/aries_cloudagent/protocols/issue_credential/v2_0/handlers/cred_request_handler.py index 43c80f36d2..af1ab58956 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/handlers/cred_request_handler.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/handlers/cred_request_handler.py @@ -1,7 +1,7 @@ """Credential request message handler.""" from .....core.oob_processor import OobMessageProcessor -from .....anoncreds.anoncreds.issuer import AnonCredsIssuerError +from .....anoncreds.issuer import AnonCredsIssuerError from .....ledger.error import LedgerError from .....messaging.base_handler import BaseHandler, HandlerException from .....messaging.models.base import BaseModelError diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/routes.py b/aries_cloudagent/protocols/issue_credential/v2_0/routes.py index d6b0a481a8..735dd9250a 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/routes.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/routes.py @@ -20,8 +20,8 @@ from ....admin.request_context import AdminRequestContext from ....connections.models.conn_record import ConnRecord from ....core.profile import Profile -from ....anoncreds.anoncreds.holder import AnonCredsHolderError -from ....anoncreds.anoncreds.issuer import AnonCredsIssuerError +from ....anoncreds.holder import AnonCredsHolderError +from ....anoncreds.issuer import AnonCredsIssuerError from ....ledger.error import LedgerError from ....messaging.decorators.attach_decorator import AttachDecorator from ....messaging.models.base import BaseModelError diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/tests/test_manager.py b/aries_cloudagent/protocols/issue_credential/v2_0/tests/test_manager.py index 4758c6a81e..45b42b47be 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/tests/test_manager.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/tests/test_manager.py @@ -9,7 +9,7 @@ from .....cache.base import BaseCache from .....cache.in_memory import InMemoryCache from .....core.in_memory import InMemoryProfile -from .....anoncreds.anoncreds.issuer import AnonCredsIssuer +from .....anoncreds.issuer import AnonCredsIssuer from .....messaging.decorators.thread_decorator import ThreadDecorator from .....messaging.decorators.attach_decorator import AttachDecorator from .....messaging.responder import BaseResponder, MockResponder diff --git a/aries_cloudagent/protocols/present_proof/indy/pres_exch_handler.py b/aries_cloudagent/protocols/present_proof/indy/pres_exch_handler.py index 75f58b0c55..aea39cc9e5 100644 --- a/aries_cloudagent/protocols/present_proof/indy/pres_exch_handler.py +++ b/aries_cloudagent/protocols/present_proof/indy/pres_exch_handler.py @@ -4,8 +4,8 @@ import time from typing import Union -from ....anoncreds.anoncreds.registry import AnonCredsRegistry -from ....anoncreds.anoncreds.holder import AnonCredsHolder, AnonCredsHolderError +from ....anoncreds.registry import AnonCredsRegistry +from ....anoncreds.holder import AnonCredsHolder, AnonCredsHolderError from ....anoncreds.models.xform import indy_proof_req2non_revoc_intervals from ....core.error import BaseError from ....core.profile import Profile diff --git a/aries_cloudagent/protocols/present_proof/v1_0/handlers/presentation_request_handler.py b/aries_cloudagent/protocols/present_proof/v1_0/handlers/presentation_request_handler.py index e60df9e1ec..8d72e9cc74 100644 --- a/aries_cloudagent/protocols/present_proof/v1_0/handlers/presentation_request_handler.py +++ b/aries_cloudagent/protocols/present_proof/v1_0/handlers/presentation_request_handler.py @@ -1,7 +1,7 @@ """Presentation request message handler.""" from .....core.oob_processor import OobMessageProcessor -from .....anoncreds.anoncreds.holder import AnonCredsHolder, AnonCredsHolderError +from .....anoncreds.holder import AnonCredsHolder, AnonCredsHolderError from .....anoncreds.models.xform import indy_proof_req_preview2indy_requested_creds from .....ledger.error import LedgerError from .....messaging.base_handler import BaseHandler, HandlerException diff --git a/aries_cloudagent/protocols/present_proof/v1_0/handlers/tests/test_presentation_request_handler.py b/aries_cloudagent/protocols/present_proof/v1_0/handlers/tests/test_presentation_request_handler.py index bfc3f6310d..a834cd40d3 100644 --- a/aries_cloudagent/protocols/present_proof/v1_0/handlers/tests/test_presentation_request_handler.py +++ b/aries_cloudagent/protocols/present_proof/v1_0/handlers/tests/test_presentation_request_handler.py @@ -2,7 +2,7 @@ from ......core.oob_processor import OobMessageProcessor -from ......anoncreds.anoncreds.holder import AnonCredsHolder +from ......anoncreds.holder import AnonCredsHolder from ......anoncreds.models.pres_preview import ( IndyPresAttrSpec, IndyPresPredSpec, diff --git a/aries_cloudagent/protocols/present_proof/v1_0/manager.py b/aries_cloudagent/protocols/present_proof/v1_0/manager.py index e2cf7f1c89..208483a2c8 100644 --- a/aries_cloudagent/protocols/present_proof/v1_0/manager.py +++ b/aries_cloudagent/protocols/present_proof/v1_0/manager.py @@ -8,7 +8,7 @@ from ....connections.models.conn_record import ConnRecord from ....core.error import BaseError from ....core.profile import Profile -from ....anoncreds.anoncreds.verifier import AnonCredsVerifier +from ....anoncreds.verifier import AnonCredsVerifier from ....messaging.decorators.attach_decorator import AttachDecorator from ....messaging.responder import BaseResponder from ....storage.error import StorageNotFoundError diff --git a/aries_cloudagent/protocols/present_proof/v1_0/routes.py b/aries_cloudagent/protocols/present_proof/v1_0/routes.py index 36b01baed0..901abc86fd 100644 --- a/aries_cloudagent/protocols/present_proof/v1_0/routes.py +++ b/aries_cloudagent/protocols/present_proof/v1_0/routes.py @@ -14,7 +14,7 @@ from ....admin.request_context import AdminRequestContext from ....connections.models.conn_record import ConnRecord -from ....anoncreds.anoncreds.holder import AnonCredsHolder, AnonCredsHolderError +from ....anoncreds.holder import AnonCredsHolder, AnonCredsHolderError from ....anoncreds.models.cred_precis import IndyCredPrecisSchema from ....anoncreds.models.proof import IndyPresSpecSchema from ....anoncreds.models.proof_request import IndyProofRequestSchema diff --git a/aries_cloudagent/protocols/present_proof/v1_0/tests/test_manager.py b/aries_cloudagent/protocols/present_proof/v1_0/tests/test_manager.py index 8807c96018..aa278d64d2 100644 --- a/aries_cloudagent/protocols/present_proof/v1_0/tests/test_manager.py +++ b/aries_cloudagent/protocols/present_proof/v1_0/tests/test_manager.py @@ -9,15 +9,15 @@ ) from .....core.in_memory import InMemoryProfile -from .....anoncreds.anoncreds.holder import AnonCredsHolder, AnonCredsHolderError -from .....anoncreds.anoncreds.issuer import AnonCredsIssuer +from .....anoncreds.holder import AnonCredsHolder, AnonCredsHolderError +from .....anoncreds.issuer import AnonCredsIssuer from .....anoncreds.models.xform import indy_proof_req_preview2indy_requested_creds from .....anoncreds.models.pres_preview import ( IndyPresAttrSpec, IndyPresPreview, IndyPresPredSpec, ) -from .....anoncreds.anoncreds.verifier import AnonCredsVerifier +from .....anoncreds.verifier import AnonCredsVerifier from .....ledger.base import BaseLedger from .....ledger.multiple_ledger.ledger_requests_executor import ( IndyLedgerRequestsExecutor, diff --git a/aries_cloudagent/protocols/present_proof/v1_0/tests/test_routes.py b/aries_cloudagent/protocols/present_proof/v1_0/tests/test_routes.py index 10e972a780..e3a84d778f 100644 --- a/aries_cloudagent/protocols/present_proof/v1_0/tests/test_routes.py +++ b/aries_cloudagent/protocols/present_proof/v1_0/tests/test_routes.py @@ -5,9 +5,9 @@ from marshmallow import ValidationError from .....admin.request_context import AdminRequestContext -from .....anoncreds.anoncreds.holder import AnonCredsHolder +from .....anoncreds.holder import AnonCredsHolder from .....anoncreds.models.proof_request import IndyProofReqAttrSpecSchema -from .....anoncreds.anoncreds.verifier import AnonCredsVerifier +from .....anoncreds.verifier import AnonCredsVerifier from .....ledger.base import BaseLedger from .....storage.error import StorageNotFoundError diff --git a/aries_cloudagent/protocols/present_proof/v2_0/formats/indy/handler.py b/aries_cloudagent/protocols/present_proof/v2_0/formats/indy/handler.py index c37b1c5f00..42dad332b7 100644 --- a/aries_cloudagent/protocols/present_proof/v2_0/formats/indy/handler.py +++ b/aries_cloudagent/protocols/present_proof/v2_0/formats/indy/handler.py @@ -6,13 +6,13 @@ from marshmallow import RAISE from typing import Mapping, Tuple -from ......anoncreds.anoncreds.holder import AnonCredsHolder +from ......anoncreds.holder import AnonCredsHolder from ......anoncreds.models.predicate import Predicate from ......anoncreds.models.proof import IndyProofSchema from ......anoncreds.models.proof_request import IndyProofRequestSchema from ......anoncreds.models.xform import indy_proof_req_preview2indy_requested_creds from ......anoncreds.util import generate_pr_nonce -from ......anoncreds.anoncreds.verifier import AnonCredsVerifier +from ......anoncreds.verifier import AnonCredsVerifier from ......messaging.decorators.attach_decorator import AttachDecorator from ......messaging.util import canon diff --git a/aries_cloudagent/protocols/present_proof/v2_0/handlers/pres_request_handler.py b/aries_cloudagent/protocols/present_proof/v2_0/handlers/pres_request_handler.py index 4e0195b4ec..6ad3729c51 100644 --- a/aries_cloudagent/protocols/present_proof/v2_0/handlers/pres_request_handler.py +++ b/aries_cloudagent/protocols/present_proof/v2_0/handlers/pres_request_handler.py @@ -1,7 +1,7 @@ """Presentation request message handler.""" from .....core.oob_processor import OobMessageProcessor -from .....anoncreds.anoncreds.holder import AnonCredsHolderError +from .....anoncreds.holder import AnonCredsHolderError from .....ledger.error import LedgerError from .....messaging.base_handler import BaseHandler, HandlerException from .....messaging.models.base import BaseModelError diff --git a/aries_cloudagent/protocols/present_proof/v2_0/handlers/tests/test_pres_request_handler.py b/aries_cloudagent/protocols/present_proof/v2_0/handlers/tests/test_pres_request_handler.py index e38511afff..18e49355ca 100644 --- a/aries_cloudagent/protocols/present_proof/v2_0/handlers/tests/test_pres_request_handler.py +++ b/aries_cloudagent/protocols/present_proof/v2_0/handlers/tests/test_pres_request_handler.py @@ -2,7 +2,7 @@ from copy import deepcopy from ......core.oob_processor import OobMessageProcessor -from ......anoncreds.anoncreds.holder import AnonCredsHolder +from ......anoncreds.holder import AnonCredsHolder from ......messaging.decorators.attach_decorator import AttachDecorator from ......messaging.request_context import RequestContext from ......messaging.responder import MockResponder diff --git a/aries_cloudagent/protocols/present_proof/v2_0/routes.py b/aries_cloudagent/protocols/present_proof/v2_0/routes.py index aa7fe17e0a..96783f8c18 100644 --- a/aries_cloudagent/protocols/present_proof/v2_0/routes.py +++ b/aries_cloudagent/protocols/present_proof/v2_0/routes.py @@ -15,7 +15,7 @@ from ....admin.request_context import AdminRequestContext from ....connections.models.conn_record import ConnRecord -from ....anoncreds.anoncreds.holder import AnonCredsHolder, AnonCredsHolderError +from ....anoncreds.holder import AnonCredsHolder, AnonCredsHolderError from ....anoncreds.models.cred_precis import IndyCredPrecisSchema from ....anoncreds.models.proof import IndyPresSpecSchema from ....anoncreds.models.proof_request import IndyProofRequestSchema diff --git a/aries_cloudagent/protocols/present_proof/v2_0/tests/test_manager.py b/aries_cloudagent/protocols/present_proof/v2_0/tests/test_manager.py index e61e880791..f8130b83e6 100644 --- a/aries_cloudagent/protocols/present_proof/v2_0/tests/test_manager.py +++ b/aries_cloudagent/protocols/present_proof/v2_0/tests/test_manager.py @@ -6,14 +6,14 @@ from asynctest import mock as async_mock, TestCase as AsyncTestCase from .....core.in_memory import InMemoryProfile -from .....anoncreds.anoncreds.holder import AnonCredsHolder +from .....anoncreds.holder import AnonCredsHolder from .....anoncreds.models.xform import indy_proof_req_preview2indy_requested_creds from .....anoncreds.models.pres_preview import ( IndyPresAttrSpec, IndyPresPreview, IndyPresPredSpec, ) -from .....anoncreds.anoncreds.verifier import AnonCredsVerifier +from .....anoncreds.verifier import AnonCredsVerifier from .....ledger.base import BaseLedger from .....ledger.multiple_ledger.ledger_requests_executor import ( IndyLedgerRequestsExecutor, diff --git a/aries_cloudagent/protocols/present_proof/v2_0/tests/test_routes.py b/aries_cloudagent/protocols/present_proof/v2_0/tests/test_routes.py index 12e3417c82..0422fb4d5c 100644 --- a/aries_cloudagent/protocols/present_proof/v2_0/tests/test_routes.py +++ b/aries_cloudagent/protocols/present_proof/v2_0/tests/test_routes.py @@ -6,9 +6,9 @@ from unittest.mock import ANY from .....admin.request_context import AdminRequestContext -from .....anoncreds.anoncreds.holder import AnonCredsHolder +from .....anoncreds.holder import AnonCredsHolder from .....anoncreds.models.proof_request import IndyProofReqAttrSpecSchema -from .....anoncreds.anoncreds.verifier import AnonCredsVerifier +from .....anoncreds.verifier import AnonCredsVerifier from .....ledger.base import BaseLedger from .....storage.error import StorageNotFoundError from .....storage.vc_holder.base import VCHolder diff --git a/aries_cloudagent/revocation/manager.py b/aries_cloudagent/revocation/manager.py index 8494d02e0e..7350261623 100644 --- a/aries_cloudagent/revocation/manager.py +++ b/aries_cloudagent/revocation/manager.py @@ -9,7 +9,7 @@ ) from ..core.error import BaseError from ..core.profile import Profile -from ..anoncreds.anoncreds.issuer import AnonCredsIssuer +from ..anoncreds.issuer import AnonCredsIssuer from ..storage.error import StorageNotFoundError from .indy import IndyRevocation from .models.issuer_cred_rev_record import IssuerCredRevRecord diff --git a/aries_cloudagent/revocation/models/issuer_rev_reg_record.py b/aries_cloudagent/revocation/models/issuer_rev_reg_record.py index 50b79e44c4..a2d633ab8d 100644 --- a/aries_cloudagent/revocation/models/issuer_rev_reg_record.py +++ b/aries_cloudagent/revocation/models/issuer_rev_reg_record.py @@ -13,7 +13,7 @@ from marshmallow import fields, validate from ...core.profile import Profile, ProfileSession -from ...anoncreds.anoncreds.issuer import AnonCredsIssuer, AnonCredsIssuerError +from ...anoncreds.issuer import AnonCredsIssuer, AnonCredsIssuerError from ...anoncreds.models.revocation import ( IndyRevRegDef, IndyRevRegDefSchema, diff --git a/aries_cloudagent/revocation/models/tests/test_issuer_rev_reg_record.py b/aries_cloudagent/revocation/models/tests/test_issuer_rev_reg_record.py index 24abc2b5e7..5390b47dc5 100644 --- a/aries_cloudagent/revocation/models/tests/test_issuer_rev_reg_record.py +++ b/aries_cloudagent/revocation/models/tests/test_issuer_rev_reg_record.py @@ -5,7 +5,7 @@ from asynctest import TestCase as AsyncTestCase, mock as async_mock from ....core.in_memory import InMemoryProfile -from ....anoncreds.anoncreds.issuer import AnonCredsIssuer, AnonCredsIssuerError +from ....anoncreds.issuer import AnonCredsIssuer, AnonCredsIssuerError from ....anoncreds.models.revocation import IndyRevRegDef from ....anoncreds.util import indy_client_dir from ....ledger.base import BaseLedger diff --git a/aries_cloudagent/revocation/routes.py b/aries_cloudagent/revocation/routes.py index da1e519c25..69f9230dee 100644 --- a/aries_cloudagent/revocation/routes.py +++ b/aries_cloudagent/revocation/routes.py @@ -22,7 +22,7 @@ from ..connections.models.conn_record import ConnRecord from ..core.event_bus import Event, EventBus from ..core.profile import Profile -from ..anoncreds.anoncreds.issuer import AnonCredsIssuerError +from ..anoncreds.issuer import AnonCredsIssuerError from ..ledger.base import BaseLedger from ..ledger.multiple_ledger.base_manager import BaseMultipleLedgerManager from ..ledger.error import LedgerError diff --git a/aries_cloudagent/revocation/tests/test_manager.py b/aries_cloudagent/revocation/tests/test_manager.py index 3cf5ae1bec..5fe2c86bc7 100644 --- a/aries_cloudagent/revocation/tests/test_manager.py +++ b/aries_cloudagent/revocation/tests/test_manager.py @@ -8,7 +8,7 @@ ) from ...core.in_memory import InMemoryProfile -from ...anoncreds.anoncreds.issuer import AnonCredsIssuer +from ...anoncreds.issuer import AnonCredsIssuer from ...protocols.issue_credential.v1_0.models.credential_exchange import ( V10CredentialExchange, ) From f4ba884eaf0c20c56bb4141cbb690329f26e70ba Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Thu, 30 Mar 2023 20:56:43 -0400 Subject: [PATCH 096/150] fix: revocation registry size circular dep Signed-off-by: Daniel Bluhm --- .../anoncreds/models/anoncreds_cred_def.py | 22 ++----------------- aries_cloudagent/messaging/valid.py | 15 +++++++------ 2 files changed, 10 insertions(+), 27 deletions(-) diff --git a/aries_cloudagent/anoncreds/models/anoncreds_cred_def.py b/aries_cloudagent/anoncreds/models/anoncreds_cred_def.py index 51b0e55fdc..7385b6d6f7 100644 --- a/aries_cloudagent/anoncreds/models/anoncreds_cred_def.py +++ b/aries_cloudagent/anoncreds/models/anoncreds_cred_def.py @@ -4,28 +4,10 @@ from anoncreds import CredentialDefinition from marshmallow import EXCLUDE, fields -from marshmallow.validate import OneOf, Regexp +from marshmallow.validate import OneOf from ...messaging.models.base import BaseModel, BaseModelSchema - - -# TODO: remove after solving circular dependency issue -class NumericStrWhole(Regexp): - """Validate value against whole number numeric string.""" - - EXAMPLE = "0" - PATTERN = r"^[0-9]*$" - - def __init__(self): - """Initializer.""" - - super().__init__( - NumericStrWhole.PATTERN, - error="Value {input} is not a non-negative numeric string", - ) - - -NUM_STR_WHOLE = {"validate": NumericStrWhole(), "example": NumericStrWhole.EXAMPLE} +from ...messaging.valid import NUM_STR_WHOLE class CredDefValuePrimary(BaseModel): diff --git a/aries_cloudagent/messaging/valid.py b/aries_cloudagent/messaging/valid.py index 5af2a2d7be..e904638c83 100644 --- a/aries_cloudagent/messaging/valid.py +++ b/aries_cloudagent/messaging/valid.py @@ -12,7 +12,6 @@ from .util import epoch_to_str from ..ledger.endpoint_type import EndpointType as EndpointTypeEnum -from ..revocation.models.revocation_registry import RevocationRegistry from ..wallet.did_posture import DIDPosture as DIDPostureEnum B58 = alphabet if isinstance(alphabet, str) else alphabet.decode("ascii") @@ -160,17 +159,19 @@ class IndyRevRegSize(Range): """Validate value as indy revocation registry size.""" EXAMPLE = 1000 + MIN_SIZE = 4 + MAX_SIZE = 32768 def __init__(self): """Initializer.""" super().__init__( - min=RevocationRegistry.MIN_SIZE, - max=RevocationRegistry.MAX_SIZE, + min=self.MIN_SIZE, + max=self.MAX_SIZE, error=( "Value {input} must be an integer between " - f"{RevocationRegistry.MIN_SIZE} and " - f"{RevocationRegistry.MAX_SIZE} inclusively" + f"{self.MIN_SIZE} and " + f"{self.MAX_SIZE} inclusively" ), ) @@ -180,8 +181,8 @@ def __call__(self, value): if type(value) != int: raise ValidationError( "Value {input} must be an integer between " - f"{RevocationRegistry.MIN_SIZE} and " - f"{RevocationRegistry.MAX_SIZE} inclusively" + f"{self.MIN_SIZE} and " + f"{self.MAX_SIZE} inclusively" ) super().__call__(value) From ccbcfcfe74d06da4198065499cd85d3386684b68 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Fri, 31 Mar 2023 17:40:29 -0400 Subject: [PATCH 097/150] feat: revocation updates Signed-off-by: Daniel Bluhm --- .../anoncreds/default/legacy_indy/registry.py | 82 +++++++++++++---- .../anoncreds/models/anoncreds_revocation.py | 59 ++++++++---- aries_cloudagent/anoncreds/routes.py | 89 ++++++++++++++----- 3 files changed, 177 insertions(+), 53 deletions(-) diff --git a/aries_cloudagent/anoncreds/default/legacy_indy/registry.py b/aries_cloudagent/anoncreds/default/legacy_indy/registry.py index 0a02da788f..8b88f0bb88 100644 --- a/aries_cloudagent/anoncreds/default/legacy_indy/registry.py +++ b/aries_cloudagent/anoncreds/default/legacy_indy/registry.py @@ -3,6 +3,7 @@ import re from asyncio import shield from typing import Optional, Pattern +from urllib.parse import urlparse from ....config.injection_context import InjectionContext from ....core.profile import Profile @@ -28,6 +29,9 @@ from ...models.anoncreds_revocation import ( GetRevStatusListResult, AnonCredsRegistryGetRevocationRegistryDefinition, + RevRegDef, + RevRegDefResult, + RevRegDefState, ) from ...models.anoncreds_schema import ( GetSchemaResult, @@ -89,6 +93,14 @@ def make_cred_def_id( return f"{cred_def.issuer_id}:3:{signature_type}:{seq_no}:{tag}" + @staticmethod + def make_rev_reg_def_id(rev_reg_def: RevRegDef) -> str: + """Derive the ID for a revocation registry definition.""" + return ( + f"{rev_reg_def.issuer_id}:4:{rev_reg_def.cred_def_id}:" + f"{rev_reg_def.type}:{rev_reg_def.tag}" + ) + async def get_schema(self, profile: Profile, schema_id: str) -> GetSchemaResult: """Get a schema from the registry.""" @@ -334,29 +346,67 @@ async def get_revocation_registry_definition( async def get_revocation_registry_definitions(self, profile: Profile, filter: str): """Get credential definition ids filtered by filter""" - # TODO: determine keyword arguments + def _check_url(self, url) -> None: + parsed = urlparse(url) + if not (parsed.scheme and parsed.netloc and parsed.path): + raise RevocationError("URI {} is not a valid URL".format(url)) + async def register_revocation_registry_definition( self, profile: Profile, - rev_reg_id: str, - issuer_id: str, - ): + revocation_registry_definition: RevRegDef, + options: Optional[dict] = None, + ) -> RevRegDefResult: """Register a revocation registry definition on the registry.""" + tails_base_url = profile.settings.get("tails_server_base_url") + if not tails_base_url: + raise AnonCredsRegistrationError("tails_server_base_url not configured") + + rev_reg_def_id = self.make_rev_reg_def_id(revocation_registry_definition) + revocation_registry_definition.value.tails_location = ( + tails_base_url.rstrip("/") + f"/{rev_reg_def_id}" + ) + try: - revoc = IndyRevocation(profile) - rev_reg = await revoc.get_issuer_rev_reg_record(rev_reg_id) + self._check_url(revocation_registry_definition.value.tails_location) + + # Translate anoncreds object to indy object + indy_rev_reg_def = { + "ver": "1.0", + "id": rev_reg_def_id, + "revocDefType": revocation_registry_definition.type, + "credDefId": revocation_registry_definition.cred_def_id, + "tag": revocation_registry_definition.tag, + "value": { + "issuanceType": "ISSUANCE_BY_DEFAULT", + "maxCredNum": revocation_registry_definition.value.max_cred_num, + "publicKeys": revocation_registry_definition.value.public_keys, + "tailsHash": revocation_registry_definition.value.tails_hash, + "tailsLocation": revocation_registry_definition.value.tails_location, + }, + } + + ledger = profile.inject(BaseLedger) + async with ledger: + resp = await ledger.send_revoc_reg_def( + indy_rev_reg_def, + revocation_registry_definition.issuer_id, + ) + seq_no = resp["result"]["txnMetadata"]["seqNo"] + except LedgerError as err: + raise AnonCredsRegistrationError() from err - await rev_reg.send_def( - profile, - write_ledger=True, - endorser_did=issuer_id, - ) - LOGGER.debug("published rev reg definition: %s", rev_reg_id) - except StorageNotFoundError as err: - raise AnonCredsRegistrationError(err) - except RevocationError as err: - raise AnonCredsRegistrationError(err) + return RevRegDefResult( + job_id=None, + revocation_registry_definition_state=RevRegDefState( + state=RevRegDefState.STATE_FINISHED, + revocation_registry_definition_id=rev_reg_def_id, + revocation_registry_definition=revocation_registry_definition, + ), + registration_metadata={}, + revocation_registry_definition_metadata={"seqNo": seq_no}, + ) async def get_revocation_status_list( self, profile: Profile, revocation_registry_id: str, timestamp: str diff --git a/aries_cloudagent/anoncreds/models/anoncreds_revocation.py b/aries_cloudagent/anoncreds/models/anoncreds_revocation.py index c15883ca40..84df3f5c33 100644 --- a/aries_cloudagent/anoncreds/models/anoncreds_revocation.py +++ b/aries_cloudagent/anoncreds/models/anoncreds_revocation.py @@ -9,6 +9,44 @@ from ...messaging.models.base import BaseModel, BaseModelSchema +class RevRegDefValue(BaseModel): + """RevRegDefValue model.""" + + class Meta: + """RevRegDefValue metadata.""" + + schema_class = "RevRegDefValueSchema" + + def __init__( + self, + public_keys: dict, + max_cred_num: int, + tails_location: str, + tails_hash: str, + **kwargs, + ): + super().__init__(**kwargs) + self.public_keys = public_keys + self.max_cred_num = max_cred_num + self.tails_location = tails_location + self.tails_hash = tails_hash + + +class RevRegDefValueSchema(BaseModelSchema): + """RevRegDefValue schema.""" + + class Meta: + """RevRegDefValueSchema metadata.""" + + model_class = RevRegDefValue + unknown = EXCLUDE + + public_keys = fields.Dict(data_key="publicKeys") + max_cred_num = fields.Int(data_key="maxCredNum") + tails_location = fields.Str(data_key="tailsLocation") + tails_hash = fields.Str(data_key="tailsHash") + + class RevRegDef(BaseModel): """RevRegDef""" @@ -23,11 +61,7 @@ def __init__( type: Literal["CL_ACCUM"], cred_def_id: str, tag: str, - # TODO: determine type for `publicKeys` - public_keys: dict, - max_cred_num: int, - tails_Location: str, - tails_hash: str, + value: RevRegDefValue, **kwargs, ): super().__init__(**kwargs) @@ -35,10 +69,7 @@ def __init__( self.type = type self.cred_def_id = cred_def_id self.tag = tag - self.public_keys = public_keys - self.max_cred_num = max_cred_num - self.tails_location = tails_Location - self.tails_hash = tails_hash + self.value = value @classmethod def from_native(cls, rev_reg_def: RevocationRegistryDefinition): @@ -63,17 +94,13 @@ class Meta: description="Issuer Identifier of the credential definition or schema", data_key="issuerId", ) - type = fields.Str() + type = fields.Str(data_key="revocDefType") cred_def_id = fields.Str( description="Credential definition identifier", data_key="credDefId", ) - tag = fields.Str(description="""""") - # TODO: type for public key - public_keys = fields.Dict(data_key="publicKeys") - max_cred_num = fields.Int(data_key="maxCredNum") - tails_location = fields.Str(data_key="tailsLocation") - tails_hash = fields.Str(data_key="tailsHash") + tag = fields.Str(description="tag for the revocation registry definition") + value = fields.Nested(RevRegDefValueSchema()) class RevRegDefState(BaseModel): diff --git a/aries_cloudagent/anoncreds/routes.py b/aries_cloudagent/anoncreds/routes.py index b8afedd052..b3cd9e9191 100644 --- a/aries_cloudagent/anoncreds/routes.py +++ b/aries_cloudagent/anoncreds/routes.py @@ -14,7 +14,7 @@ from .registry import AnonCredsRegistry from .issuer import AnonCredsIssuer from .models.anoncreds_cred_def import ( - CredDefSchema, + CredDefResultSchema, GetCredDefResultSchema, ) from .models.anoncreds_schema import ( @@ -22,6 +22,7 @@ SchemaResultSchema, GetSchemaResultSchema, ) +from .models.anoncreds_revocation import RevRegDefResultSchema from ..admin.request_context import AdminRequestContext from ..messaging.models.openapi import OpenAPISchema @@ -89,25 +90,6 @@ class CredDefsQueryStringSchema(OpenAPISchema): ) -class CredDefState(OpenAPISchema): - """Parameters and validators for credential definition state.""" - - state = fields.Str() # TODO: create validator for only possible states - credential_definition_id = fields.Str( - description="Credential definition identifier", - ) - credential_definition = fields.Nested(CredDefSchema()) - - -class PostCredDefResponseSchema(OpenAPISchema): - """Parameters and validators for credential definition create response.""" - - job_id = fields.Str() - credential_definition_state = fields.Nested(CredDefState()) - registration_metadata = fields.Dict() - credential_definition_metadata = fields.Dict() - - class SchemaPostOptionSchema(OpenAPISchema): """Parameters and validators for schema options.""" @@ -252,7 +234,7 @@ async def schemas_get(request: web.BaseRequest): @docs(tags=["anoncreds"], summary="") @request_schema(CredDefPostRequestSchema()) -@response_schema(PostCredDefResponseSchema(), 200, description="") +@response_schema(CredDefResultSchema(), 200, description="") async def cred_def_post(request: web.BaseRequest): """Request handler for creating . @@ -280,6 +262,70 @@ async def cred_def_post(request: web.BaseRequest): return web.json_response(result.serialize()) +class RevRegCreateRequestSchema(OpenAPISchema): + """Request schema for revocation registry creation request.""" + + issuer_id = fields.Str( + description="Issuer Identifier of the credential definition or schema", + data_key="issuerId", + ) + cred_def_id = fields.Str( + description="Credential definition identifier", + data_key="credDefId", + ) + tag = fields.Str(description="tag for revocation registry") + max_cred_num = fields.Int(data_key="maxCredNum") + registry_type = fields.Str( + description="Revocation registry type", + data_key="type", + required=False, + ) + + +@docs(tags=["anoncreds"], summary="") +@request_schema(RevRegCreateRequestSchema()) +@response_schema(RevRegDefResultSchema(), 200, description="") +async def rev_reg_def_post(request: web.BaseRequest): + """Request handler for creating . + + Args: + + + (method) def create_and_register_revocation_registry_definition( + issuer_id: str, + cred_def_id: str, + tag: str, + max_cred_num: int, + registry_type: str, + tails_base_path: str, + options: dict[Unknown, Unknown] | None = None + ) -> Coroutine[Any, Any, RevRegDefResult] + + Returns: + + """ + context: AdminRequestContext = request["context"] + body = await request.json() + options = body.get("options") + issuer_id = body.get("issuerId") + cred_def_id = body.get("credDefId") + tag = body.get("tag") + max_cred_num = body.get("maxCredNum") + registry_type = body.get("type") + + issuer = AnonCredsIssuer(context.profile) + result = await issuer.create_and_register_revocation_registry_definition( + issuer_id, + cred_def_id, + tag, + max_cred_num, + registry_type=registry_type, + options=options, + ) + + return web.json_response(result.serialize()) + + @docs(tags=["anoncreds"], summary="") @match_info_schema(CredIdMatchInfo()) @response_schema(GetCredDefResultSchema(), 200, description="") @@ -353,6 +399,7 @@ async def register(app: web.Application): cred_defs_get, allow_head=False, ), + web.post("/anoncreds/revocation-registry-definition", rev_reg_def_post), ] ) From 452515b327507f4d3802b550f79ecb1461891f10 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Fri, 31 Mar 2023 17:43:53 -0400 Subject: [PATCH 098/150] refactor: drop indy reqs on rev objects Signed-off-by: Daniel Bluhm --- aries_cloudagent/revocation/models/indy.py | 69 ------------------- .../models/issuer_cred_rev_record.py | 6 -- .../models/issuer_rev_reg_record.py | 8 +-- .../revocation/models/tests/test_indy.py | 44 ------------ 4 files changed, 2 insertions(+), 125 deletions(-) delete mode 100644 aries_cloudagent/revocation/models/indy.py delete mode 100644 aries_cloudagent/revocation/models/tests/test_indy.py diff --git a/aries_cloudagent/revocation/models/indy.py b/aries_cloudagent/revocation/models/indy.py deleted file mode 100644 index b826698511..0000000000 --- a/aries_cloudagent/revocation/models/indy.py +++ /dev/null @@ -1,69 +0,0 @@ -"""Indy utilities for revocation.""" - -from time import time - -from marshmallow import fields - -from ...messaging.models.base import BaseModel, BaseModelSchema -from ...messaging.valid import INT_EPOCH - - -class NonRevocationInterval(BaseModel): - """Indy non-revocation interval.""" - - class Meta: - """NonRevocationInterval metadata.""" - - schema_class = "NonRevocationIntervalSchema" - - def __init__(self, fro: int = None, to: int = None, **kwargs): - """Initialize non-revocation interval. - - Args: - fro: earliest time of interest - to: latest time of interest - - """ - super().__init__(**kwargs) - self.fro = fro - self.to = to - - def covers(self, timestamp: 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 NonRevocationIntervalSchema(BaseModelSchema): - """Schema to allow serialization/deserialization of non-revocation intervals.""" - - class Meta: - """NonRevocationIntervalSchema metadata.""" - - model_class = NonRevocationInterval - - fro = fields.Int( - required=False, - description="Earliest time of interest in non-revocation interval", - data_key="from", - strict=True, - **INT_EPOCH - ) - to = fields.Int( - required=False, - description="Latest time of interest in non-revocation interval", - strict=True, - **INT_EPOCH - ) diff --git a/aries_cloudagent/revocation/models/issuer_cred_rev_record.py b/aries_cloudagent/revocation/models/issuer_cred_rev_record.py index 10ba69ef53..b9fc9c0555 100644 --- a/aries_cloudagent/revocation/models/issuer_cred_rev_record.py +++ b/aries_cloudagent/revocation/models/issuer_cred_rev_record.py @@ -7,9 +7,6 @@ from ...core.profile import ProfileSession from ...messaging.models.base_record import BaseRecord, BaseRecordSchema from ...messaging.valid import ( - INDY_CRED_DEF_ID, - INDY_CRED_REV_ID, - INDY_REV_REG_ID, UUIDFour, ) @@ -152,17 +149,14 @@ class Meta: rev_reg_id = fields.Str( required=False, description="Revocation registry identifier", - **INDY_REV_REG_ID, ) cred_def_id = fields.Str( required=False, description="Credential definition identifier", - **INDY_CRED_DEF_ID, ) cred_rev_id = fields.Str( required=False, description="Credential revocation identifier", - **INDY_CRED_REV_ID, ) cred_ex_version = fields.Str( required=False, diff --git a/aries_cloudagent/revocation/models/issuer_rev_reg_record.py b/aries_cloudagent/revocation/models/issuer_rev_reg_record.py index a2d633ab8d..6b8338f709 100644 --- a/aries_cloudagent/revocation/models/issuer_rev_reg_record.py +++ b/aries_cloudagent/revocation/models/issuer_rev_reg_record.py @@ -26,9 +26,6 @@ from ...messaging.models.base_record import BaseRecord, BaseRecordSchema from ...messaging.valid import ( BASE58_SHA256_HASH, - INDY_CRED_DEF_ID, - INDY_DID, - INDY_REV_REG_ID, UUIDFour, ) from ...tails.base import BaseTailsServer @@ -577,14 +574,13 @@ class Meta: cred_def_id = fields.Str( required=False, description="Credential definition identifier", - **INDY_CRED_DEF_ID, ) error_msg = fields.Str( required=False, description="Error message", example="Revocation registry undefined", ) - issuer_did = fields.Str(required=False, description="Issuer DID", **INDY_DID) + issuer_did = fields.Str(required=False, description="Issuer DID") max_cred_num = fields.Int( required=False, description="Maximum number of credentials for revocation registry", @@ -598,7 +594,7 @@ class Meta: validate=validate.Equal("CL_ACCUM"), ) revoc_reg_id = fields.Str( - required=False, description="Revocation registry identifier", **INDY_REV_REG_ID + required=False, description="Revocation registry identifier" ) revoc_reg_def = fields.Nested( IndyRevRegDefSchema(), diff --git a/aries_cloudagent/revocation/models/tests/test_indy.py b/aries_cloudagent/revocation/models/tests/test_indy.py deleted file mode 100644 index 8c02057708..0000000000 --- a/aries_cloudagent/revocation/models/tests/test_indy.py +++ /dev/null @@ -1,44 +0,0 @@ -from unittest import TestCase - -from asynctest import TestCase as AsyncTestCase -from asynctest import mock as async_mock - -import pytest - -from ..indy import NonRevocationInterval - -FROM = 1000000000 -TO = 1234567890 - -INTERVAL_FROM = NonRevocationInterval(fro=FROM) -INTERVAL_TO = NonRevocationInterval(to=TO) -INTERVAL = NonRevocationInterval(fro=FROM, to=TO) - - -class TestInterval(TestCase): - """Non-revocation interval tests""" - - def test_serde(self): - """Test serialization and deserialization.""" - for interval in (INTERVAL_FROM, INTERVAL_TO, INTERVAL): - non_revo_dict = interval.serialize() - assert non_revo_dict.get("from") == interval.fro - assert non_revo_dict.get("to") == interval.to - - model = NonRevocationInterval.deserialize(non_revo_dict) - assert model.fro == interval.fro and model.to == interval.to - assert model.timestamp() - - def test_covers(self): - """Test spanning check.""" - assert INTERVAL_FROM.covers(FROM) - assert INTERVAL_FROM.covers(TO) - assert INTERVAL_FROM.covers() - - assert INTERVAL_TO.covers(FROM) - assert INTERVAL_TO.covers(TO) - assert not INTERVAL_TO.covers() - - assert INTERVAL.covers(FROM) - assert INTERVAL.covers(TO) - assert not INTERVAL.covers() From 76a6c1553cd6884797dca22f71f94fcc5c8d298a Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Fri, 31 Mar 2023 17:47:25 -0400 Subject: [PATCH 099/150] refactor: rename IndyRevocation -> AnonCredsRevocation Signed-off-by: Daniel Bluhm --- .../anoncreds/default/legacy_indy/registry.py | 4 +-- .../credential_definitions/routes.py | 4 +-- .../issue_credential/v1_0/manager.py | 6 ++-- .../v2_0/formats/indy/handler.py | 6 ++-- .../revocation/{indy.py => anoncreds.py} | 10 +++---- aries_cloudagent/revocation/manager.py | 4 +-- aries_cloudagent/revocation/routes.py | 28 +++++++++---------- .../revocation/tests/test_indy.py | 10 +++---- 8 files changed, 36 insertions(+), 36 deletions(-) rename aries_cloudagent/revocation/{indy.py => anoncreds.py} (96%) diff --git a/aries_cloudagent/anoncreds/default/legacy_indy/registry.py b/aries_cloudagent/anoncreds/default/legacy_indy/registry.py index 8b88f0bb88..09eda429fd 100644 --- a/aries_cloudagent/anoncreds/default/legacy_indy/registry.py +++ b/aries_cloudagent/anoncreds/default/legacy_indy/registry.py @@ -16,7 +16,7 @@ ) from ....multitenant.base import BaseMultitenantManager from ....revocation.error import RevocationError -from ....revocation.indy import IndyRevocation +from ....revocation.anoncreds import AnonCredsRevocation from ....storage.error import StorageNotFoundError from ...issuer import AnonCredsIssuer, AnonCredsIssuerError from ...models.anoncreds_cred_def import ( @@ -335,7 +335,7 @@ async def get_revocation_registry_definition( """Get a revocation registry definition from the registry.""" try: - revoc = IndyRevocation(profile) + revoc = AnonCredsRevocation(profile) rev_reg = await revoc.get_issuer_rev_reg_record(rev_reg_id) except StorageNotFoundError as err: raise AnonCredsResolutionError(err) diff --git a/aries_cloudagent/messaging/credential_definitions/routes.py b/aries_cloudagent/messaging/credential_definitions/routes.py index 239da9f19f..e09d9b0c81 100644 --- a/aries_cloudagent/messaging/credential_definitions/routes.py +++ b/aries_cloudagent/messaging/credential_definitions/routes.py @@ -41,7 +41,7 @@ get_endorser_connection_id, ) -from ...revocation.indy import IndyRevocation +from ...revocation.anoncreds import AnonCredsRevocation from ...storage.base import BaseStorage, StorageRecord from ...storage.error import StorageError @@ -519,7 +519,7 @@ async def on_cred_def_event(profile: Profile, event: Event): # For a cred def we also automatically create a second "pending" revocation # registry, so when the first one fills up we can continue to issue credentials # without a delay - revoc = IndyRevocation(profile) + revoc = AnonCredsRevocation(profile) await revoc.init_issuer_registry( cred_def_id, rev_reg_size, diff --git a/aries_cloudagent/protocols/issue_credential/v1_0/manager.py b/aries_cloudagent/protocols/issue_credential/v1_0/manager.py index 2396684c47..d6504cc664 100644 --- a/aries_cloudagent/protocols/issue_credential/v1_0/manager.py +++ b/aries_cloudagent/protocols/issue_credential/v1_0/manager.py @@ -26,7 +26,7 @@ ) from ....messaging.responder import BaseResponder from ....multitenant.base import BaseMultitenantManager -from ....revocation.indy import IndyRevocation +from ....revocation.anoncreds import AnonCredsRevocation from ....revocation.models.issuer_cred_rev_record import IssuerCredRevRecord from ....revocation.models.revocation_registry import RevocationRegistry from ....storage.base import BaseStorage @@ -642,7 +642,7 @@ async def issue_credential( await asyncio.sleep(2) if revocable: - revoc = IndyRevocation(self._profile) + revoc = AnonCredsRevocation(self._profile) registry_info = await revoc.get_or_create_active_registry( cred_def_id ) @@ -670,7 +670,7 @@ async def issue_credential( continue if revocable and rev_reg.max_creds <= int(cred_rev_id): - revoc = IndyRevocation(self._profile) + revoc = AnonCredsRevocation(self._profile) await revoc.handle_full_registry(rev_reg_id) del revoc diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/handler.py b/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/handler.py index 90d656ef2a..a46b0285f9 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/handler.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/handler.py @@ -28,7 +28,7 @@ ) from ......messaging.decorators.attach_decorator import AttachDecorator from ......multitenant.base import BaseMultitenantManager -from ......revocation.indy import IndyRevocation +from ......revocation.anoncreds import AnonCredsRevocation from ......revocation.models.issuer_cred_rev_record import IssuerCredRevRecord from ......revocation.models.revocation_registry import RevocationRegistry from ......storage.base import BaseStorage @@ -345,7 +345,7 @@ async def issue_credential( if revocable: # TODO make this go through the anoncreds interface - revoc = IndyRevocation(self.profile) + revoc = AnonCredsRevocation(self.profile) registry_info = await revoc.get_or_create_active_registry(cred_def_id) if not registry_info: continue @@ -371,7 +371,7 @@ async def issue_credential( continue if revocable and rev_reg.max_creds <= int(cred_rev_id): - revoc = IndyRevocation(self.profile) + revoc = AnonCredsRevocation(self.profile) await revoc.handle_full_registry(rev_reg_id) del revoc diff --git a/aries_cloudagent/revocation/indy.py b/aries_cloudagent/revocation/anoncreds.py similarity index 96% rename from aries_cloudagent/revocation/indy.py rename to aries_cloudagent/revocation/anoncreds.py index 57c2af53cf..2e03e56c0b 100644 --- a/aries_cloudagent/revocation/indy.py +++ b/aries_cloudagent/revocation/anoncreds.py @@ -27,13 +27,13 @@ from .util import notify_revocation_reg_init_event -class IndyRevocation: +class AnonCredsRevocation: """Class for managing Indy credential revocation.""" REV_REG_CACHE = {} def __init__(self, profile: Profile): - """Initialize the IndyRevocation instance.""" + """Initialize the AnonCredsRevocation instance.""" self._profile = profile async def init_issuer_registry( @@ -212,8 +212,8 @@ async def get_or_create_active_registry( async def get_ledger_registry(self, revoc_reg_id: str) -> RevocationRegistry: """Get a revocation registry from the ledger, fetching as necessary.""" - if revoc_reg_id in IndyRevocation.REV_REG_CACHE: - return IndyRevocation.REV_REG_CACHE[revoc_reg_id] + if revoc_reg_id in AnonCredsRevocation.REV_REG_CACHE: + return AnonCredsRevocation.REV_REG_CACHE[revoc_reg_id] ledger = await self.get_ledger_for_registry(revoc_reg_id) @@ -221,7 +221,7 @@ async def get_ledger_registry(self, revoc_reg_id: str) -> RevocationRegistry: rev_reg = RevocationRegistry.from_definition( await ledger.get_revoc_reg_def(revoc_reg_id), True ) - IndyRevocation.REV_REG_CACHE[revoc_reg_id] = rev_reg + AnonCredsRevocation.REV_REG_CACHE[revoc_reg_id] = rev_reg return rev_reg async def get_ledger_for_registry(self, revoc_reg_id: str) -> BaseLedger: diff --git a/aries_cloudagent/revocation/manager.py b/aries_cloudagent/revocation/manager.py index 7350261623..d477e087b3 100644 --- a/aries_cloudagent/revocation/manager.py +++ b/aries_cloudagent/revocation/manager.py @@ -11,7 +11,7 @@ from ..core.profile import Profile from ..anoncreds.issuer import AnonCredsIssuer from ..storage.error import StorageNotFoundError -from .indy import IndyRevocation +from .indy import AnonCredsRevocation from .models.issuer_cred_rev_record import IssuerCredRevRecord from .models.issuer_rev_reg_record import IssuerRevRegRecord from .util import notify_pending_cleared_event, notify_revocation_published_event @@ -109,7 +109,7 @@ async def revoke_credential( """ issuer = AnonCredsIssuer(self._profile) - revoc = IndyRevocation(self._profile) + revoc = AnonCredsRevocation(self._profile) issuer_rr_rec = await revoc.get_issuer_rev_reg_record(rev_reg_id) if not issuer_rr_rec: raise RevocationManagerError( diff --git a/aries_cloudagent/revocation/routes.py b/aries_cloudagent/revocation/routes.py index 69f9230dee..7550fcfc58 100644 --- a/aries_cloudagent/revocation/routes.py +++ b/aries_cloudagent/revocation/routes.py @@ -54,7 +54,7 @@ from ..storage.error import StorageError, StorageNotFoundError from .error import RevocationError, RevocationNotSupportedError -from .indy import IndyRevocation +from .indy import AnonCredsRevocation from .manager import RevocationManager, RevocationManagerError from .models.issuer_cred_rev_record import ( IssuerCredRevRecord, @@ -573,7 +573,7 @@ async def create_rev_reg(request: web.BaseRequest): ) try: - revoc = IndyRevocation(profile) + revoc = AnonCredsRevocation(profile) issuer_rev_reg_rec = await revoc.init_issuer_registry( credential_definition_id, max_cred_num=max_cred_num, @@ -649,7 +649,7 @@ async def get_rev_reg(request: web.BaseRequest): rev_reg_id = request.match_info["rev_reg_id"] try: - revoc = IndyRevocation(context.profile) + revoc = AnonCredsRevocation(context.profile) rev_reg = await revoc.get_issuer_rev_reg_record(rev_reg_id) except StorageNotFoundError as err: raise web.HTTPNotFound(reason=err.roll_up) from err @@ -746,7 +746,7 @@ async def get_rev_reg_indy_recs(request: web.BaseRequest): rev_reg_id = request.match_info["rev_reg_id"] - revoc = IndyRevocation(context.profile) + revoc = AnonCredsRevocation(context.profile) rev_reg_delta = await revoc.get_issuer_rev_reg_delta(rev_reg_id) return web.json_response( @@ -905,7 +905,7 @@ async def get_active_rev_reg(request: web.BaseRequest): cred_def_id = request.match_info["cred_def_id"] try: - revoc = IndyRevocation(context.profile) + revoc = AnonCredsRevocation(context.profile) rev_reg = await revoc.get_active_issuer_rev_reg_record(cred_def_id) except StorageNotFoundError as err: raise web.HTTPNotFound(reason=err.roll_up) from err @@ -936,7 +936,7 @@ async def get_tails_file(request: web.BaseRequest) -> web.FileResponse: rev_reg_id = request.match_info["rev_reg_id"] try: - revoc = IndyRevocation(context.profile) + revoc = AnonCredsRevocation(context.profile) rev_reg = await revoc.get_issuer_rev_reg_record(rev_reg_id) except StorageNotFoundError as err: raise web.HTTPNotFound(reason=err.roll_up) from err @@ -962,7 +962,7 @@ async def upload_tails_file(request: web.BaseRequest): rev_reg_id = request.match_info["rev_reg_id"] try: - revoc = IndyRevocation(context.profile) + revoc = AnonCredsRevocation(context.profile) rev_reg = await revoc.get_issuer_rev_reg_record(rev_reg_id) except StorageNotFoundError as err: raise web.HTTPNotFound(reason=err.roll_up) from err @@ -1047,7 +1047,7 @@ async def send_rev_reg_def(request: web.BaseRequest): endorser_did = endorser_info["endorser_did"] try: - revoc = IndyRevocation(profile) + revoc = AnonCredsRevocation(profile) rev_reg = await revoc.get_issuer_rev_reg_record(rev_reg_id) rev_reg_resp = await rev_reg.send_def( @@ -1161,7 +1161,7 @@ async def send_rev_reg_entry(request: web.BaseRequest): endorser_did = endorser_info["endorser_did"] try: - revoc = IndyRevocation(profile) + revoc = AnonCredsRevocation(profile) rev_reg = await revoc.get_issuer_rev_reg_record(rev_reg_id) rev_entry_resp = await rev_reg.send_entry( profile, @@ -1234,7 +1234,7 @@ async def update_rev_reg(request: web.BaseRequest): rev_reg_id = request.match_info["rev_reg_id"] try: - revoc = IndyRevocation(profile) + revoc = AnonCredsRevocation(profile) rev_reg = await revoc.get_issuer_rev_reg_record(rev_reg_id) await rev_reg.set_tails_file_public_uri(profile, tails_public_uri) @@ -1267,7 +1267,7 @@ async def set_rev_reg_state(request: web.BaseRequest): state = request.query.get("state") try: - revoc = IndyRevocation(profile) + revoc = AnonCredsRevocation(profile) rev_reg = await revoc.get_issuer_rev_reg_record(rev_reg_id) async with profile.session() as session: await rev_reg.set_state(session, state) @@ -1382,7 +1382,7 @@ async def generate(rr_record: IssuerRevRegRecord) -> dict: "create_pending_rev_reg", False ) if write_ledger and create_pending_rev_reg: - revoc = IndyRevocation(profile) + revoc = AnonCredsRevocation(profile) await revoc.init_issuer_registry( registry_record.cred_def_id, registry_record.max_cred_num, @@ -1460,7 +1460,7 @@ async def on_revocation_registry_endorsed_event(profile: Profile, event: Event): """Handle revocation registry endorsement event.""" meta_data = event.payload rev_reg_id = meta_data["context"]["rev_reg_id"] - revoc = IndyRevocation(profile) + revoc = AnonCredsRevocation(profile) registry_record = await revoc.get_issuer_rev_reg_record(rev_reg_id) if profile.settings.get_value("endorser.auto_request"): @@ -1501,7 +1501,7 @@ async def delete_tails(request: web.BaseRequest) -> json: context: AdminRequestContext = request["context"] rev_reg_id = request.query.get("rev_reg_id") cred_def_id = request.query.get("cred_def_id") - revoc = IndyRevocation(context.profile) + revoc = AnonCredsRevocation(context.profile) session = revoc._profile.session() if rev_reg_id: rev_reg = await revoc.get_issuer_rev_reg_record(rev_reg_id) diff --git a/aries_cloudagent/revocation/tests/test_indy.py b/aries_cloudagent/revocation/tests/test_indy.py index 08fd5eb828..4e477d6f25 100644 --- a/aries_cloudagent/revocation/tests/test_indy.py +++ b/aries_cloudagent/revocation/tests/test_indy.py @@ -15,12 +15,12 @@ RevocationNotSupportedError, RevocationRegistryBadSizeError, ) -from ..indy import IndyRevocation +from ..indy import AnonCredsRevocation from ..models.issuer_rev_reg_record import DEFAULT_REGISTRY_SIZE, IssuerRevRegRecord from ..models.revocation_registry import RevocationRegistry -class TestIndyRevocation(AsyncTestCase): +class TestAnonCredsRevocation(AsyncTestCase): def setUp(self): self.profile = InMemoryProfile.test_profile() self.context = self.profile.context @@ -40,7 +40,7 @@ def setUp(self): ) ), ) - self.revoc = IndyRevocation(self.profile) + self.revoc = AnonCredsRevocation(self.profile) self.test_did = "sample-did" @@ -162,7 +162,7 @@ async def test_get_ledger_registry(self): ) as mock_from_def: result = await self.revoc.get_ledger_registry("dummy") assert result == mock_from_def.return_value - assert "dummy" in IndyRevocation.REV_REG_CACHE + assert "dummy" in AnonCredsRevocation.REV_REG_CACHE await self.revoc.get_ledger_registry("dummy") @@ -183,7 +183,7 @@ async def test_get_ledger_registry(self): ) as mock_from_def: result = await self.revoc.get_ledger_registry("dummy2") assert result == mock_from_def.return_value - assert "dummy2" in IndyRevocation.REV_REG_CACHE + assert "dummy2" in AnonCredsRevocation.REV_REG_CACHE await self.revoc.get_ledger_registry("dummy2") From a4e2a69b1f50ad037970930962197f140c684264 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Fri, 31 Mar 2023 18:44:35 -0400 Subject: [PATCH 100/150] refactor: de-indy-ify AnonCredsRevocation Signed-off-by: Daniel Bluhm --- aries_cloudagent/anoncreds/base.py | 2 +- aries_cloudagent/anoncreds/registry.py | 2 +- aries_cloudagent/revocation/anoncreds.py | 89 +++++------------------- aries_cloudagent/revocation/manager.py | 4 +- aries_cloudagent/revocation/routes.py | 37 +++++----- 5 files changed, 41 insertions(+), 93 deletions(-) diff --git a/aries_cloudagent/anoncreds/base.py b/aries_cloudagent/anoncreds/base.py index 11ed0042b7..2c0b0ffd3d 100644 --- a/aries_cloudagent/anoncreds/base.py +++ b/aries_cloudagent/anoncreds/base.py @@ -111,7 +111,7 @@ async def get_revocation_registry_definition( @abstractmethod async def get_revocation_status_list( - self, profile: Profile, revocation_registry_id: str, timestamp: str + self, profile: Profile, revocation_registry_id: str, timestamp: int ) -> GetRevStatusListResult: """Get a revocation list from the registry.""" diff --git a/aries_cloudagent/anoncreds/registry.py b/aries_cloudagent/anoncreds/registry.py index 13d980f60b..4297532eda 100644 --- a/aries_cloudagent/anoncreds/registry.py +++ b/aries_cloudagent/anoncreds/registry.py @@ -141,7 +141,7 @@ async def register_revocation_registry_definition( ) async def get_revocation_status_list( - self, profile: Profile, revocation_registry_id: str, timestamp: str + self, profile: Profile, revocation_registry_id: str, timestamp: int ) -> GetRevStatusListResult: """Get a revocation list from the registry.""" resolver = await self._resolver_for_identifier(revocation_registry_id) diff --git a/aries_cloudagent/revocation/anoncreds.py b/aries_cloudagent/revocation/anoncreds.py index 2e03e56c0b..987080104e 100644 --- a/aries_cloudagent/revocation/anoncreds.py +++ b/aries_cloudagent/revocation/anoncreds.py @@ -3,20 +3,13 @@ from typing import Optional, Sequence, Tuple from uuid import uuid4 +from ..anoncreds.registry import AnonCredsRegistry from ..core.profile import Profile -from ..ledger.base import BaseLedger -from ..ledger.multiple_ledger.ledger_requests_executor import ( - GET_CRED_DEF, - GET_REVOC_REG_DEF, - IndyLedgerRequestsExecutor, -) -from ..multitenant.base import BaseMultitenantManager from ..protocols.endorse_transaction.v1_0.util import ( get_endorser_connection_id, is_author_role, ) from ..storage.base import StorageNotFoundError - from .error import ( RevocationError, RevocationNotSupportedError, @@ -47,22 +40,11 @@ async def init_issuer_registry( notify: bool = True, ) -> IssuerRevRegRecord: """Create a new revocation registry record for a credential definition.""" - 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) - if not cred_def: - raise RevocationNotSupportedError("Credential definition not found") - if not cred_def["value"].get("revocation"): + anoncreds_registry = self._profile.inject(AnonCredsRegistry) + result = await anoncreds_registry.get_credential_definition( + self._profile, cred_def_id + ) + if not result.credential_definition.value.revocation: raise RevocationNotSupportedError( "Credential definition does not support revocation" ) @@ -161,26 +143,6 @@ async def list_issuer_registries(self) -> Sequence[IssuerRevRegRecord]: async with self._profile.session() as session: return await IssuerRevRegRecord.query(session) - async def get_issuer_rev_reg_delta( - self, rev_reg_id: str, fro: int = None, to: int = None - ) -> dict: - """ - Check ledger for revocation status for a given revocation registry. - - Args: - rev_reg_id: ID of the revocation registry - - """ - ledger = await self.get_ledger_for_registry(rev_reg_id) - async with ledger: - (rev_reg_delta, _) = await ledger.get_revoc_reg_delta( - rev_reg_id, - fro, - to, - ) - - return rev_reg_delta - async def get_or_create_active_registry( self, cred_def_id: str, max_cred_num: int = None ) -> Optional[Tuple[IssuerRevRegRecord, RevocationRegistry]]: @@ -210,31 +172,14 @@ async def get_or_create_active_registry( ) return None - async def get_ledger_registry(self, revoc_reg_id: str) -> RevocationRegistry: - """Get a revocation registry from the ledger, fetching as necessary.""" - if revoc_reg_id in AnonCredsRevocation.REV_REG_CACHE: - return AnonCredsRevocation.REV_REG_CACHE[revoc_reg_id] - - ledger = await self.get_ledger_for_registry(revoc_reg_id) - - async with ledger: - rev_reg = RevocationRegistry.from_definition( - await ledger.get_revoc_reg_def(revoc_reg_id), True - ) - AnonCredsRevocation.REV_REG_CACHE[revoc_reg_id] = rev_reg - return rev_reg - - async def get_ledger_for_registry(self, revoc_reg_id: str) -> BaseLedger: - """Get the ledger for the given registry.""" - 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( - revoc_reg_id, - txn_record_type=GET_REVOC_REG_DEF, - ) - )[1] - return ledger + async def get_revocation_registry(self, revoc_reg_id: str) -> RevocationRegistry: + """Return a revocation registry by identifier and hydrate.""" + anoncreds_registry = self._profile.inject(AnonCredsRegistry) + result = await anoncreds_registry.get_revocation_registry_definition( + self._profile, revoc_reg_id + ) + rev_reg = RevocationRegistry.from_definition( + result.revocation_registry.serialize(), True + ) + AnonCredsRevocation.REV_REG_CACHE[revoc_reg_id] = rev_reg + return rev_reg diff --git a/aries_cloudagent/revocation/manager.py b/aries_cloudagent/revocation/manager.py index d477e087b3..a5e3927f5d 100644 --- a/aries_cloudagent/revocation/manager.py +++ b/aries_cloudagent/revocation/manager.py @@ -11,7 +11,7 @@ from ..core.profile import Profile from ..anoncreds.issuer import AnonCredsIssuer from ..storage.error import StorageNotFoundError -from .indy import AnonCredsRevocation +from .anoncreds import AnonCredsRevocation from .models.issuer_cred_rev_record import IssuerCredRevRecord from .models.issuer_rev_reg_record import IssuerRevRegRecord from .util import notify_pending_cleared_event, notify_revocation_published_event @@ -130,7 +130,7 @@ async def revoke_credential( await rev_notify_rec.save(session, reason="New revocation notification") if publish: - rev_reg = await revoc.get_ledger_registry(rev_reg_id) + rev_reg = await revoc.get_revocation_registry(rev_reg_id) await rev_reg.get_or_fetch_local_tails_path() # pick up pending revocations on input revocation registry crids = (issuer_rr_rec.pending_pub or []) + [cred_rev_id] diff --git a/aries_cloudagent/revocation/routes.py b/aries_cloudagent/revocation/routes.py index 7550fcfc58..3d3c2bc5c6 100644 --- a/aries_cloudagent/revocation/routes.py +++ b/aries_cloudagent/revocation/routes.py @@ -1,11 +1,12 @@ """Revocation registry admin routes.""" +from asyncio import shield import json import logging import os -import shutil -from asyncio import shield import re +from time import time +import shutil from aiohttp import web from aiohttp_apispec import ( @@ -19,13 +20,14 @@ from marshmallow.exceptions import ValidationError from ..admin.request_context import AdminRequestContext +from ..anoncreds.issuer import AnonCredsIssuerError +from ..anoncreds.registry import AnonCredsRegistry from ..connections.models.conn_record import ConnRecord from ..core.event_bus import Event, EventBus from ..core.profile import Profile -from ..anoncreds.issuer import AnonCredsIssuerError from ..ledger.base import BaseLedger -from ..ledger.multiple_ledger.base_manager import BaseMultipleLedgerManager from ..ledger.error import LedgerError +from ..ledger.multiple_ledger.base_manager import BaseMultipleLedgerManager from ..messaging.credential_definitions.util import CRED_DEF_SENT_RECORD_TYPE from ..messaging.models.base import BaseModelError from ..messaging.models.openapi import OpenAPISchema @@ -36,8 +38,8 @@ INDY_REV_REG_ID, INDY_REV_REG_SIZE, UUID4, - WHOLE_NUM, UUIDFour, + WHOLE_NUM, ) from ..protocols.endorse_transaction.v1_0.manager import ( TransactionManager, @@ -47,14 +49,13 @@ TransactionRecordSchema, ) from ..protocols.endorse_transaction.v1_0.util import ( - is_author_role, get_endorser_connection_id, + is_author_role, ) from ..storage.base import BaseStorage from ..storage.error import StorageError, StorageNotFoundError - +from .anoncreds import AnonCredsRevocation from .error import RevocationError, RevocationNotSupportedError -from .indy import AnonCredsRevocation from .manager import RevocationManager, RevocationManagerError from .models.issuer_cred_rev_record import ( IssuerCredRevRecord, @@ -62,10 +63,10 @@ ) from .models.issuer_rev_reg_record import IssuerRevRegRecord, IssuerRevRegRecordSchema from .util import ( + REVOCATION_ENTRY_EVENT, REVOCATION_EVENT_PREFIX, - REVOCATION_REG_INIT_EVENT, REVOCATION_REG_ENDORSED_EVENT, - REVOCATION_ENTRY_EVENT, + REVOCATION_REG_INIT_EVENT, notify_revocation_entry_event, ) @@ -294,8 +295,8 @@ class CredRevRecordDetailsResultSchema(OpenAPISchema): class CredRevIndyRecordsResultSchema(OpenAPISchema): """Result schema for revoc reg delta.""" - rev_reg_delta = fields.Dict( - description="Indy revocation registry delta", + rev_status_list = fields.Dict( + description="revocation status list", ) @@ -731,7 +732,7 @@ async def get_rev_reg_issued(request: web.BaseRequest): ) @match_info_schema(RevRegIdMatchInfoSchema()) @response_schema(CredRevIndyRecordsResultSchema(), 200, description="") -async def get_rev_reg_indy_recs(request: web.BaseRequest): +async def get_rev_status_list(request: web.BaseRequest): """ Request handler to get details of revoked credentials from ledger. @@ -746,12 +747,14 @@ async def get_rev_reg_indy_recs(request: web.BaseRequest): rev_reg_id = request.match_info["rev_reg_id"] - revoc = AnonCredsRevocation(context.profile) - rev_reg_delta = await revoc.get_issuer_rev_reg_delta(rev_reg_id) + anoncreds_registry = context.inject(AnonCredsRegistry) + rev_status_list = await anoncreds_registry.get_revocation_status_list( + context.profile, rev_reg_id, int(time()) + ) return web.json_response( { - "rev_reg_delta": rev_reg_delta, + "rev_status_list": rev_status_list, } ) @@ -1575,7 +1578,7 @@ async def register(app: web.Application): ), web.get( "/revocation/registry/{rev_reg_id}/issued/indy_recs", - get_rev_reg_indy_recs, + get_rev_status_list, allow_head=False, ), web.post("/revocation/create-registry", create_rev_reg), From d277ecc1ea09408bc3ff4bf84ccad5f0320e5840 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Fri, 31 Mar 2023 19:33:37 -0400 Subject: [PATCH 101/150] refactor: issuer rev reg record usage from anoncreds Signed-off-by: Daniel Bluhm --- aries_cloudagent/anoncreds/issuer.py | 6 +- .../anoncreds/models/anoncreds_revocation.py | 4 + aries_cloudagent/anoncreds/routes.py | 55 +++++---- aries_cloudagent/revocation/anoncreds.py | 11 +- .../models/issuer_rev_reg_record.py | 114 +++++++++++++----- .../revocation/models/revocation_registry.py | 14 +-- aries_cloudagent/revocation/recover.py | 2 + aries_cloudagent/revocation/routes.py | 13 +- 8 files changed, 139 insertions(+), 80 deletions(-) diff --git a/aries_cloudagent/anoncreds/issuer.py b/aries_cloudagent/anoncreds/issuer.py index 32ae9ab263..807ee45083 100644 --- a/aries_cloudagent/anoncreds/issuer.py +++ b/aries_cloudagent/anoncreds/issuer.py @@ -564,7 +564,7 @@ async def get_created_revocation_registry_definitions( return [entry.name for entry in rev_reg_defs] async def create_and_register_revocation_status_list( - self, rev_reg_def_id: str, timestamp: int, options: dict + self, rev_reg_def_id: str, options: dict ): """Create and register a revocation status list.""" try: @@ -585,7 +585,9 @@ async def create_and_register_revocation_status_list( issuer_id = rev_reg_def_entry.value_json["issuerId"] rev_status_list = RevocationStatusList.create( - rev_reg_def_id, rev_reg_def_entry.raw_value, issuer_id, timestamp + rev_reg_def_id, + rev_reg_def_entry.raw_value, + issuer_id, ) anoncreds_registry = self.profile.inject(AnonCredsRegistry) diff --git a/aries_cloudagent/anoncreds/models/anoncreds_revocation.py b/aries_cloudagent/anoncreds/models/anoncreds_revocation.py index 84df3f5c33..66a93eac02 100644 --- a/aries_cloudagent/anoncreds/models/anoncreds_revocation.py +++ b/aries_cloudagent/anoncreds/models/anoncreds_revocation.py @@ -184,6 +184,10 @@ def rev_reg_def_id(self): self.revocation_registry_definition_state.revocation_registry_definition_id ) + @property + def rev_reg_def(self): + return self.revocation_registry_definition_state.revocation_registry_definition + class RevRegDefResultSchema(BaseModelSchema): """Cred def result schema.""" diff --git a/aries_cloudagent/anoncreds/routes.py b/aries_cloudagent/anoncreds/routes.py index b3cd9e9191..c57721a557 100644 --- a/aries_cloudagent/anoncreds/routes.py +++ b/aries_cloudagent/anoncreds/routes.py @@ -1,4 +1,5 @@ """Anoncreds admin routes.""" +from asyncio import shield import logging from aiohttp import web @@ -11,24 +12,21 @@ ) from marshmallow import fields -from .registry import AnonCredsRegistry +from aries_cloudagent.revocation.error import RevocationNotSupportedError + +from ..admin.request_context import AdminRequestContext +from ..messaging.models.openapi import OpenAPISchema +from ..messaging.valid import UUIDFour +from ..revocation.anoncreds import AnonCredsRevocation from .issuer import AnonCredsIssuer -from .models.anoncreds_cred_def import ( - CredDefResultSchema, - GetCredDefResultSchema, -) +from .models.anoncreds_cred_def import CredDefResultSchema, GetCredDefResultSchema +from .models.anoncreds_revocation import RevRegDefResultSchema from .models.anoncreds_schema import ( AnonCredsSchemaSchema, - SchemaResultSchema, GetSchemaResultSchema, + SchemaResultSchema, ) -from .models.anoncreds_revocation import RevRegDefResultSchema - -from ..admin.request_context import AdminRequestContext -from ..messaging.models.openapi import OpenAPISchema -from ..messaging.valid import ( - UUIDFour, -) +from .registry import AnonCredsRegistry LOGGER = logging.getLogger(__name__) @@ -306,22 +304,31 @@ async def rev_reg_def_post(request: web.BaseRequest): """ context: AdminRequestContext = request["context"] body = await request.json() - options = body.get("options") issuer_id = body.get("issuerId") cred_def_id = body.get("credDefId") - tag = body.get("tag") max_cred_num = body.get("maxCredNum") - registry_type = body.get("type") + options = body.get("options") issuer = AnonCredsIssuer(context.profile) - result = await issuer.create_and_register_revocation_registry_definition( - issuer_id, - cred_def_id, - tag, - max_cred_num, - registry_type=registry_type, - options=options, - ) + # check we published this cred def + found = await issuer.match_created_credential_definitions(cred_def_id) + if not found: + raise web.HTTPNotFound( + reason=f"Not issuer of credential definition id {cred_def_id}" + ) + + try: + revoc = AnonCredsRevocation(context.profile) + issuer_rev_reg_rec = await revoc.init_issuer_registry( + issuer_id, + cred_def_id, + max_cred_num=max_cred_num, + options=options, + notify=False, + ) + except RevocationNotSupportedError as e: + raise web.HTTPBadRequest(reason=e.message) from e + result = await shield(issuer_rev_reg_rec.generate_and_publish(context.profile)) return web.json_response(result.serialize()) diff --git a/aries_cloudagent/revocation/anoncreds.py b/aries_cloudagent/revocation/anoncreds.py index 987080104e..03a513b942 100644 --- a/aries_cloudagent/revocation/anoncreds.py +++ b/aries_cloudagent/revocation/anoncreds.py @@ -31,12 +31,14 @@ def __init__(self, profile: Profile): async def init_issuer_registry( self, + issuer_id: str, cred_def_id: str, max_cred_num: int = None, revoc_def_type: str = None, tag: str = None, create_pending_rev_reg: bool = False, endorser_connection_id: str = None, + options: Optional[dict] = None, notify: bool = True, ) -> IssuerRevRegRecord: """Create a new revocation registry record for a credential definition.""" @@ -56,19 +58,16 @@ async def init_issuer_registry( ) record_id = str(uuid4()) - issuer_did = cred_def_id.split(":")[0] record = IssuerRevRegRecord( new_with_id=True, record_id=record_id, cred_def_id=cred_def_id, - issuer_did=issuer_did, + issuer_id=issuer_id, max_cred_num=max_cred_num, revoc_def_type=revoc_def_type, - tag=tag, + tag=tag or record_id, + options=options, ) - revoc_def_type = record.revoc_def_type - rtag = record.tag or record_id - record.revoc_reg_id = f"{issuer_did}:4:{cred_def_id}:{revoc_def_type}:{rtag}" async with self._profile.session() as session: await record.save(session, reason="Init revocation registry") diff --git a/aries_cloudagent/revocation/models/issuer_rev_reg_record.py b/aries_cloudagent/revocation/models/issuer_rev_reg_record.py index 6b8338f709..364e78ecc0 100644 --- a/aries_cloudagent/revocation/models/issuer_rev_reg_record.py +++ b/aries_cloudagent/revocation/models/issuer_rev_reg_record.py @@ -7,18 +7,18 @@ from os.path import join from pathlib import Path from shutil import move -from typing import Any, Mapping, Sequence, Union, Tuple +from typing import Any, Mapping, Sequence, Union, Tuple, Optional from urllib.parse import urlparse from marshmallow import fields, validate from ...core.profile import Profile, ProfileSession from ...anoncreds.issuer import AnonCredsIssuer, AnonCredsIssuerError -from ...anoncreds.models.revocation import ( - IndyRevRegDef, - IndyRevRegDefSchema, - IndyRevRegEntry, - IndyRevRegEntrySchema, +from ...anoncreds.models.anoncreds_revocation import ( + RevRegDef, + RevRegDefSchema, + RevStatusList, + RevStatusListSchema, ) from ...anoncreds.util import indy_client_dir from ...ledger.base import BaseLedger @@ -56,7 +56,7 @@ class Meta: LOG_STATE_FLAG = "debug.revocation" TAG_NAMES = { "cred_def_id", - "issuer_did", + "issuer_id", "revoc_def_type", "revoc_reg_id", "state", @@ -77,17 +77,18 @@ def __init__( state: str = None, cred_def_id: str = None, error_msg: str = None, - issuer_did: str = None, + issuer_id: str = None, max_cred_num: int = None, revoc_def_type: str = None, revoc_reg_id: str = None, - revoc_reg_def: Union[IndyRevRegDef, Mapping] = None, - revoc_reg_entry: Union[IndyRevRegEntry, Mapping] = None, + revoc_reg_def: Union[RevRegDef, Mapping] = None, + rev_status_list: Union[RevStatusList, Mapping] = None, tag: str = None, tails_hash: str = None, tails_local_path: str = None, tails_public_uri: str = None, pending_pub: Sequence[str] = None, + options: Optional[dict] = None, **kwargs, ): """Initialize the issuer revocation registry record.""" @@ -96,12 +97,12 @@ def __init__( ) self.cred_def_id = cred_def_id self.error_msg = error_msg - self.issuer_did = issuer_did + self.issuer_id = issuer_id self.max_cred_num = max_cred_num or DEFAULT_REGISTRY_SIZE self.revoc_def_type = revoc_def_type or self.REVOC_DEF_TYPE_CL self.revoc_reg_id = revoc_reg_id - self._revoc_reg_def = IndyRevRegDef.serde(revoc_reg_def) - self._revoc_reg_entry = IndyRevRegEntry.serde(revoc_reg_entry) + self._revoc_reg_def = RevRegDef.serde(revoc_reg_def) + self._revoc_reg_entry = RevStatusList.serde(rev_status_list) self.tag = tag self.tails_hash = tails_hash self.tails_local_path = tails_local_path @@ -109,6 +110,7 @@ def __init__( self.pending_pub = ( sorted(list(set(pending_pub))) if pending_pub else [] ) # order for eq comparison between instances + self.options = options or {} @property def record_id(self) -> str: @@ -116,24 +118,24 @@ def record_id(self) -> str: return self._id @property - def revoc_reg_def(self) -> IndyRevRegDef: + def revoc_reg_def(self) -> RevRegDef: """Accessor; get deserialized.""" return None if self._revoc_reg_def is None else self._revoc_reg_def.de @revoc_reg_def.setter def revoc_reg_def(self, value): """Setter; store de/serialized views.""" - self._revoc_reg_def = IndyRevRegDef.serde(value) + self._revoc_reg_def = RevRegDef.serde(value) @property - def revoc_reg_entry(self) -> IndyRevRegEntry: + def rev_status_list(self) -> RevStatusList: """Accessor; get deserialized.""" return None if self._revoc_reg_entry is None else self._revoc_reg_entry.de - @revoc_reg_entry.setter - def revoc_reg_entry(self, value): + @rev_status_list.setter + def rev_status_list(self, value): """Setter; store de/serialized views.""" - self._revoc_reg_entry = IndyRevRegEntry.serde(value) + self._revoc_reg_entry = RevStatusList.serde(value) @property def record_value(self) -> Mapping: @@ -155,7 +157,7 @@ def record_value(self) -> Mapping: prop: getattr(self, f"_{prop}").ser for prop in ( "revoc_reg_def", - "revoc_reg_entry", + "rev_status_list", ) if getattr(self, prop) is not None }, @@ -166,6 +168,53 @@ def _check_url(self, url) -> None: if not (parsed.scheme and parsed.netloc and parsed.path): raise RevocationError("URI {} is not a valid URL".format(url)) + async def generate_and_publish(self, profile: Profile): + """Create the revocation registry definition and tails file and publish it.""" + if not self.tag: + self.tag = self._id or str(uuid.uuid4()) + + if self.state != IssuerRevRegRecord.STATE_INIT: + raise RevocationError( + "Revocation registry {} in state {}: cannot generate".format( + self.revoc_reg_id, self.state + ) + ) + + issuer = AnonCredsIssuer(profile) + + tails_hopper_dir = indy_client_dir(join("tails", ".hopper"), create=True) + + LOGGER.debug("Creating revocation registry with size: %d", self.max_cred_num) + + try: + result = await issuer.create_and_register_revocation_registry_definition( + self.issuer_id, + self.cred_def_id, + self.revoc_def_type, + self.tag, + self.max_cred_num, + tails_hopper_dir, + self.options, + ) + except AnonCredsIssuerError as err: + raise RevocationError() from err + + self.revoc_reg_id = result.rev_reg_def_id + self.revoc_reg_def = result.rev_reg_def.serialize() + self.state = IssuerRevRegRecord.STATE_POSTED + self.tails_hash = result.rev_reg_def.value.tails_hash + self.tails_public_uri = result.rev_reg_def.value.tails_location + + tails_dir = indy_client_dir(join("tails", self.revoc_reg_id), create=True) + tails_path = join(tails_dir, self.tails_hash) + move(join(tails_hopper_dir, self.tails_hash), tails_path) + self.tails_local_path = tails_path + + async with profile.session() as session: + await self.save(session, reason="Generated registry") + + return result + async def generate_registry(self, profile: Profile): """Create the revocation registry definition and tails file.""" if not self.tag: @@ -190,7 +239,7 @@ async def generate_registry(self, profile: Profile): revoc_reg_def_json, revoc_reg_entry_json, ) = await issuer.create_and_store_revocation_registry( - self.issuer_did, + self.issuer_id, self.cred_def_id, self.revoc_def_type, self.tag, @@ -236,7 +285,7 @@ async def send_def( endorser_did: str = None, ) -> dict: """Send the revocation registry definition to the ledger.""" - if not (self.revoc_reg_def and self.issuer_did): + if not (self.revoc_reg_def and self.issuer_id): raise RevocationError(f"Revocation registry {self.revoc_reg_id} undefined") self._check_url(self.tails_public_uri) @@ -252,7 +301,7 @@ async def send_def( async with ledger: rev_reg_res = await ledger.send_revoc_reg_def( self._revoc_reg_def.ser, - self.issuer_did, + self.issuer_id, write_ledger=write_ledger, endorser_did=endorser_did, ) @@ -273,8 +322,8 @@ async def send_entry( if not ( self.revoc_reg_id and self.revoc_def_type - and self.revoc_reg_entry - and self.issuer_did + and self.rev_status_list + and self.issuer_id ): raise RevocationError("Revocation registry undefined") @@ -298,7 +347,7 @@ async def send_entry( self.revoc_reg_id, self.revoc_def_type, self._revoc_reg_entry.ser, - self.issuer_did, + self.issuer_id, write_ledger=write_ledger, endorser_did=endorser_did, ) @@ -340,6 +389,8 @@ async def send_entry( return rev_entry_res + # TODO Update to align with rev status list (if that's even necessary; does + # this move to indy registry?) async def fix_ledger_entry( self, profile: Profile, @@ -477,7 +528,7 @@ def get_registry(self) -> RevocationRegistry: return RevocationRegistry( self.revoc_reg_id, cred_def_id=self.cred_def_id, - issuer_did=self.issuer_did, + issuer_id=self.issuer_id, max_creds=self.max_cred_num, reg_def_type=self.revoc_def_type, tag=self.tag, @@ -580,7 +631,7 @@ class Meta: description="Error message", example="Revocation registry undefined", ) - issuer_did = fields.Str(required=False, description="Issuer DID") + issuer_id = fields.Str(required=False, description="Issuer DID") max_cred_num = fields.Int( required=False, description="Maximum number of credentials for revocation registry", @@ -597,12 +648,12 @@ class Meta: required=False, description="Revocation registry identifier" ) revoc_reg_def = fields.Nested( - IndyRevRegDefSchema(), + RevRegDefSchema(), required=False, description="Revocation registry definition", ) - revoc_reg_entry = fields.Nested( - IndyRevRegEntrySchema(), required=False, description="Revocation registry entry" + rev_status_list = fields.Nested( + RevStatusListSchema(), required=False, description="Revocation registry entry" ) tag = fields.Str( required=False, description="Tag within issuer revocation registry identifier" @@ -624,3 +675,4 @@ class Meta: ), required=False, ) + options = fields.Dict(description="Arbitrary options") diff --git a/aries_cloudagent/revocation/models/revocation_registry.py b/aries_cloudagent/revocation/models/revocation_registry.py index 4d081de2ff..b8e1cf461d 100644 --- a/aries_cloudagent/revocation/models/revocation_registry.py +++ b/aries_cloudagent/revocation/models/revocation_registry.py @@ -30,7 +30,7 @@ def __init__( registry_id: str = None, *, cred_def_id: str = None, - issuer_did: str = None, + issuer_id: str = None, max_creds: int = None, reg_def_type: str = None, tag: str = None, @@ -41,7 +41,7 @@ def __init__( ): """Initialize the revocation registry instance.""" self._cred_def_id = cred_def_id - self._issuer_did = issuer_did + self.issuer_id = issuer_id self._max_creds = max_creds self._reg_def_type = reg_def_type self._registry_id = registry_id @@ -60,11 +60,11 @@ def from_definition( reg_id = revoc_reg_def["id"] tails_location = revoc_reg_def["value"]["tailsLocation"] - issuer_did_match = re.match(r"^.*?([^:]*):3:CL:.*", revoc_reg_def["credDefId"]) - issuer_did = issuer_did_match.group(1) if issuer_did_match else None + issuer_id = re.match(r"^.*?([^:]*):3:CL:.*", revoc_reg_def["credDefId"]) + issuer_id = issuer_id.group(1) if issuer_id else None init = { "cred_def_id": revoc_reg_def["credDefId"], - "issuer_did": issuer_did, + "issuer_id": issuer_id, "reg_def_type": revoc_reg_def["revocDefType"], "max_creds": revoc_reg_def["value"]["maxCredNum"], "tag": revoc_reg_def["tag"], @@ -87,9 +87,9 @@ def cred_def_id(self) -> str: return self._cred_def_id @property - def issuer_did(self) -> str: + def issuer_id(self) -> str: """Accessor for the issuer DID.""" - return self._issuer_did + return self.issuer_id @property def max_creds(self) -> int: diff --git a/aries_cloudagent/revocation/recover.py b/aries_cloudagent/revocation/recover.py index 49adcbc5ed..504c53cdbe 100644 --- a/aries_cloudagent/revocation/recover.py +++ b/aries_cloudagent/revocation/recover.py @@ -23,6 +23,8 @@ failed.) """ +# TODO This should probably be moved to an Indy plugin + class RevocRecoveryException(Exception): """Raise exception generating the recovery transaction.""" diff --git a/aries_cloudagent/revocation/routes.py b/aries_cloudagent/revocation/routes.py index 3d3c2bc5c6..a31e29bd36 100644 --- a/aries_cloudagent/revocation/routes.py +++ b/aries_cloudagent/revocation/routes.py @@ -20,7 +20,7 @@ from marshmallow.exceptions import ValidationError from ..admin.request_context import AdminRequestContext -from ..anoncreds.issuer import AnonCredsIssuerError +from ..anoncreds.issuer import AnonCredsIssuer, AnonCredsIssuerError from ..anoncreds.registry import AnonCredsRegistry from ..connections.models.conn_record import ConnRecord from ..core.event_bus import Event, EventBus @@ -28,7 +28,6 @@ from ..ledger.base import BaseLedger from ..ledger.error import LedgerError from ..ledger.multiple_ledger.base_manager import BaseMultipleLedgerManager -from ..messaging.credential_definitions.util import CRED_DEF_SENT_RECORD_TYPE from ..messaging.models.base import BaseModelError from ..messaging.models.openapi import OpenAPISchema from ..messaging.responder import BaseResponder @@ -52,7 +51,6 @@ get_endorser_connection_id, is_author_role, ) -from ..storage.base import BaseStorage from ..storage.error import StorageError, StorageNotFoundError from .anoncreds import AnonCredsRevocation from .error import RevocationError, RevocationNotSupportedError @@ -561,13 +559,8 @@ async def create_rev_reg(request: web.BaseRequest): max_cred_num = body.get("max_cred_num") # check we published this cred def - async with profile.session() as session: - storage = session.inject(BaseStorage) - - found = await storage.find_all_records( - type_filter=CRED_DEF_SENT_RECORD_TYPE, - tag_query={"cred_def_id": credential_definition_id}, - ) + issuer = AnonCredsIssuer(context.profile) + found = await issuer.match_created_credential_definitions(credential_definition_id) if not found: raise web.HTTPNotFound( reason=f"Not issuer of credential definition id {credential_definition_id}" From 195fe029b3a0eb72278480ec3399e1bbecfb7f94 Mon Sep 17 00:00:00 2001 From: Adam Burdett Date: Mon, 3 Apr 2023 16:53:26 -0600 Subject: [PATCH 102/150] start of updating send_entry+ Signed-off-by: Adam Burdett --- .../models/issuer_rev_reg_record.py | 87 ++++++++++--------- 1 file changed, 48 insertions(+), 39 deletions(-) diff --git a/aries_cloudagent/revocation/models/issuer_rev_reg_record.py b/aries_cloudagent/revocation/models/issuer_rev_reg_record.py index 364e78ecc0..830c1606b7 100644 --- a/aries_cloudagent/revocation/models/issuer_rev_reg_record.py +++ b/aries_cloudagent/revocation/models/issuer_rev_reg_record.py @@ -12,6 +12,8 @@ from marshmallow import fields, validate +from ...anoncreds.registry import AnonCredsRegistry + from ...core.profile import Profile, ProfileSession from ...anoncreds.issuer import AnonCredsIssuer, AnonCredsIssuerError from ...anoncreds.models.anoncreds_revocation import ( @@ -339,47 +341,54 @@ async def send_entry( self.revoc_reg_id, self.state ) ) + anoncreds_registry = profile.inject(AnonCredsRegistry) - ledger = profile.inject(BaseLedger) - async with ledger: - try: - rev_entry_res = await ledger.send_revoc_reg_entry( - self.revoc_reg_id, - self.revoc_def_type, - self._revoc_reg_entry.ser, - self.issuer_id, - write_ledger=write_ledger, - endorser_did=endorser_did, + try: + rev_entry_res = ( + await anoncreds_registry.register_revocation_registry_definition( + profile, + self.revoc_reg_def, # TODO: is this the correct rev_reg_def? + {}, # TODO: use options with endorser did and ... + ) + ) + """rev_entry_res = await ledger.send_revoc_reg_entry( + self.revoc_reg_id, + self.revoc_def_type, + self._revoc_reg_entry.ser, + self.issuer_id, + write_ledger=write_ledger, + endorser_did=endorser_did, + )""" + except LedgerTransactionError as err: # TODO: update errors + if "InvalidClientRequest" in err.roll_up: + # ... if the ledger write fails (with "InvalidClientRequest") + # e.g. aries_cloudagent.ledger.error.LedgerTransactionError: + # Ledger rejected transaction request: client request invalid: + # InvalidClientRequest(...) + # In this scenario we try to post a correction + LOGGER.warn("Retry ledger update/fix due to error") + LOGGER.warn(err) + """(_, _, res) = await self.fix_ledger_entry( + profile, + True, + ledger.pool.genesis_txns, ) - except LedgerTransactionError as err: - if "InvalidClientRequest" in err.roll_up: - # ... if the ledger write fails (with "InvalidClientRequest") - # e.g. aries_cloudagent.ledger.error.LedgerTransactionError: - # Ledger rejected transaction request: client request invalid: - # InvalidClientRequest(...) - # In this scenario we try to post a correction - LOGGER.warn("Retry ledger update/fix due to error") - LOGGER.warn(err) - (_, _, res) = await self.fix_ledger_entry( - profile, - True, - ledger.pool.genesis_txns, - ) - rev_entry_res = {"result": res} - LOGGER.warn("Ledger update/fix applied") - elif "InvalidClientTaaAcceptanceError" in err.roll_up: - # if no write access (with "InvalidClientTaaAcceptanceError") - # e.g. aries_cloudagent.ledger.error.LedgerTransactionError: - # Ledger rejected transaction request: client request invalid: - # InvalidClientTaaAcceptanceError(...) - LOGGER.error("Ledger update failed due to TAA issue") - LOGGER.error(err) - raise err - else: - # not sure what happened, raise an error - LOGGER.error("Ledger update failed due to unknown issue") - LOGGER.error(err) - raise err + rev_entry_res = {"result": res}""" + rev_entry_res = {"result": "res"} + LOGGER.warn("Ledger update/fix applied") + elif "InvalidClientTaaAcceptanceError" in err.roll_up: + # if no write access (with "InvalidClientTaaAcceptanceError") + # e.g. aries_cloudagent.ledger.error.LedgerTransactionError: + # Ledger rejected transaction request: client request invalid: + # InvalidClientTaaAcceptanceError(...) + LOGGER.error("Ledger update failed due to TAA issue") + LOGGER.error(err) + raise err + else: + # not sure what happened, raise an error + LOGGER.error("Ledger update failed due to unknown issue") + LOGGER.error(err) + raise err if self.state == IssuerRevRegRecord.STATE_POSTED: self.state = IssuerRevRegRecord.STATE_ACTIVE # initial entry activates async with profile.session() as session: From c35b488b669edb20367088494a3ba9442b16a9a6 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Tue, 4 Apr 2023 14:52:09 -0400 Subject: [PATCH 103/150] fix: send_entry logic moved to legacy indy Signed-off-by: Daniel Bluhm --- .../anoncreds/default/legacy_indy/registry.py | 139 +++++++++++++++++- .../models/issuer_rev_reg_record.py | 17 ++- 2 files changed, 146 insertions(+), 10 deletions(-) diff --git a/aries_cloudagent/anoncreds/default/legacy_indy/registry.py b/aries_cloudagent/anoncreds/default/legacy_indy/registry.py index 09eda429fd..25665fdc0c 100644 --- a/aries_cloudagent/anoncreds/default/legacy_indy/registry.py +++ b/aries_cloudagent/anoncreds/default/legacy_indy/registry.py @@ -2,13 +2,17 @@ import logging import re from asyncio import shield -from typing import Optional, Pattern +from typing import Optional, Pattern, Tuple from urllib.parse import urlparse from ....config.injection_context import InjectionContext from ....core.profile import Profile from ....ledger.base import BaseLedger -from ....ledger.error import LedgerError, LedgerObjectAlreadyExistsError +from ....ledger.error import ( + LedgerError, + LedgerObjectAlreadyExistsError, + LedgerTransactionError, +) from ....ledger.merkel_validation.constants import GET_SCHEMA from ....ledger.multiple_ledger.ledger_requests_executor import ( GET_CRED_DEF, @@ -32,6 +36,8 @@ RevRegDef, RevRegDefResult, RevRegDefState, + RevStatusList, + RevStatusListResult, ) from ...models.anoncreds_schema import ( GetSchemaResult, @@ -414,5 +420,132 @@ async def get_revocation_status_list( """Get a revocation list from the registry.""" # TODO: determine keyword arguments - async def register_revocation_status_list(self): + async def register_revocation_status_list( + self, + profile: Profile, + rev_reg_def: RevRegDef, + rev_status_list: RevStatusList, + options: Optional[dict] = None, + ) -> RevStatusListResult: """Register a revocation list on the registry.""" + ledger = profile.inject(BaseLedger) + + # TODO Translate rev_status_list to rev_reg_entry + rev_reg_entry = {} + + try: + rev_entry_res = await ledger.send_revoc_reg_entry( + rev_status_list.rev_reg_id, + rev_reg_def.type, + rev_reg_entry, + rev_status_list.issuer_id, + write_ledger=True, + endorser_did=None, + ) + except LedgerTransactionError as err: # TODO: update errors + if "InvalidClientRequest" in err.roll_up: + # ... if the ledger write fails (with "InvalidClientRequest") + # e.g. aries_cloudagent.ledger.error.LedgerTransactionError: + # Ledger rejected transaction request: client request invalid: + # InvalidClientRequest(...) + # In this scenario we try to post a correction + LOGGER.warn("Retry ledger update/fix due to error") + LOGGER.warn(err) + (_, _, res) = await self.fix_ledger_entry( + profile, + True, + ledger.pool.genesis_txns, + ) + rev_entry_res = {"result": res} + LOGGER.warn("Ledger update/fix applied") + elif "InvalidClientTaaAcceptanceError" in err.roll_up: + # if no write access (with "InvalidClientTaaAcceptanceError") + # e.g. aries_cloudagent.ledger.error.LedgerTransactionError: + # Ledger rejected transaction request: client request invalid: + # InvalidClientTaaAcceptanceError(...) + LOGGER.error("Ledger update failed due to TAA issue") + LOGGER.error(err) + raise err + else: + # not sure what happened, raise an error + LOGGER.error("Ledger update failed due to unknown issue") + LOGGER.error(err) + raise err + + # TODO Fill this in with rev_entry_res and rev_status_list + return RevStatusListResult() + + # TODO Adjust this to work here (moved from IssuerRevRegRecord) + async def fix_ledger_entry( + self, + profile: Profile, + apply_ledger_update: bool, + genesis_transactions: str, + ) -> Tuple[dict, dict, dict]: + """Fix the ledger entry to match wallet-recorded credentials.""" + # get rev reg delta (revocations published to ledger) + ledger = profile.inject(BaseLedger) + async with ledger: + (rev_reg_delta, _) = await ledger.get_revoc_reg_delta(self.revoc_reg_id) + + # get rev reg records from wallet (revocations and status) + recs = [] + rec_count = 0 + accum_count = 0 + recovery_txn = {} + applied_txn = {} + async with profile.session() as session: + recs = await IssuerCredRevRecord.query_by_ids( + session, rev_reg_id=self.revoc_reg_id + ) + + revoked_ids = [] + for rec in recs: + if rec.state == IssuerCredRevRecord.STATE_REVOKED: + revoked_ids.append(int(rec.cred_rev_id)) + if int(rec.cred_rev_id) not in rev_reg_delta["value"]["revoked"]: + # await rec.set_state(session, IssuerCredRevRecord.STATE_ISSUED) + rec_count += 1 + + LOGGER.debug(">>> fixed entry recs count = %s", rec_count) + LOGGER.debug( + ">>> rev_reg_record.revoc_reg_entry.value: %s", + self.revoc_reg_entry.value, + ) + LOGGER.debug( + '>>> rev_reg_delta.get("value"): %s', rev_reg_delta.get("value") + ) + + # if we had any revocation discrepencies, check the accumulator value + if rec_count > 0: + if (self.revoc_reg_entry.value and rev_reg_delta.get("value")) and not ( + self.revoc_reg_entry.value.accum == rev_reg_delta["value"]["accum"] + ): + # self.revoc_reg_entry = rev_reg_delta["value"] + # await self.save(session) + accum_count += 1 + + calculated_txn = await generate_ledger_rrrecovery_txn( + genesis_transactions, + self.revoc_reg_id, + revoked_ids, + ) + recovery_txn = json.loads(calculated_txn.to_json()) + + LOGGER.debug(">>> apply_ledger_update = %s", apply_ledger_update) + if apply_ledger_update: + ledger = session.inject_or(BaseLedger) + if not ledger: + reason = "No ledger available" + if not session.context.settings.get_value("wallet.type"): + reason += ": missing wallet-type?" + raise LedgerError(reason=reason) + + async with ledger: + ledger_response = await ledger.send_revoc_reg_entry( + self.revoc_reg_id, "CL_ACCUM", recovery_txn + ) + + applied_txn = ledger_response["result"] + + return (rev_reg_delta, recovery_txn, applied_txn) diff --git a/aries_cloudagent/revocation/models/issuer_rev_reg_record.py b/aries_cloudagent/revocation/models/issuer_rev_reg_record.py index 830c1606b7..42fb300521 100644 --- a/aries_cloudagent/revocation/models/issuer_rev_reg_record.py +++ b/aries_cloudagent/revocation/models/issuer_rev_reg_record.py @@ -20,6 +20,7 @@ RevRegDef, RevRegDefSchema, RevStatusList, + RevStatusListResult, RevStatusListSchema, ) from ...anoncreds.util import indy_client_dir @@ -217,6 +218,7 @@ async def generate_and_publish(self, profile: Profile): return result + # TODO Delete me; replaced by generate_and_publish async def generate_registry(self, profile: Profile): """Create the revocation registry definition and tails file.""" if not self.tag: @@ -267,6 +269,7 @@ async def generate_registry(self, profile: Profile): async with profile.session() as session: await self.save(session, reason="Generated registry") + # TODO Delete me? Probably not needed anymore with the generate_and_publish async def set_tails_file_public_uri(self, profile: Profile, tails_file_uri: str): """Update tails file's publicly accessible URI.""" if not (self.revoc_reg_def and self.revoc_reg_def.value.tails_location): @@ -280,6 +283,7 @@ async def set_tails_file_public_uri(self, profile: Profile, tails_file_uri: str) async with profile.session() as session: await self.save(session, reason="Set tails file public URI") + # TODO Delete me; replaced by generate_and_publish async def send_def( self, profile: Profile, @@ -317,9 +321,10 @@ async def send_def( async def send_entry( self, profile: Profile, + options: Optional[dict] = None, write_ledger: bool = True, endorser_did: str = None, - ) -> dict: + ) -> RevStatusListResult: """Send a registry entry to the ledger.""" if not ( self.revoc_reg_id @@ -344,12 +349,10 @@ async def send_entry( anoncreds_registry = profile.inject(AnonCredsRegistry) try: - rev_entry_res = ( - await anoncreds_registry.register_revocation_registry_definition( - profile, - self.revoc_reg_def, # TODO: is this the correct rev_reg_def? - {}, # TODO: use options with endorser did and ... - ) + rev_entry_res = await anoncreds_registry.register_revocation_status_list( + profile, + self.rev_status_list, # TODO: is this the correct rev_reg_def? + options, ) """rev_entry_res = await ledger.send_revoc_reg_entry( self.revoc_reg_id, From 895ae08a76d139350626a6d062639db76bba2139 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Tue, 4 Apr 2023 17:58:26 -0400 Subject: [PATCH 104/150] feat: register rev status list on legacy indy Signed-off-by: Daniel Bluhm --- aries_cloudagent/anoncreds/base.py | 1 + .../anoncreds/default/legacy_indy/registry.py | 90 +++++++++++-------- aries_cloudagent/anoncreds/issuer.py | 2 + .../anoncreds/models/anoncreds_revocation.py | 7 +- aries_cloudagent/anoncreds/registry.py | 3 +- .../models/issuer_rev_reg_record.py | 79 ++++------------ 6 files changed, 80 insertions(+), 102 deletions(-) diff --git a/aries_cloudagent/anoncreds/base.py b/aries_cloudagent/anoncreds/base.py index 2c0b0ffd3d..eb0ca72a6c 100644 --- a/aries_cloudagent/anoncreds/base.py +++ b/aries_cloudagent/anoncreds/base.py @@ -149,6 +149,7 @@ async def register_revocation_registry_definition( async def register_revocation_status_list( self, profile: Profile, + rev_reg_def: RevRegDef, rev_status_list: RevStatusList, options: Optional[dict] = None, ) -> RevStatusListResult: diff --git a/aries_cloudagent/anoncreds/default/legacy_indy/registry.py b/aries_cloudagent/anoncreds/default/legacy_indy/registry.py index 25665fdc0c..577224a094 100644 --- a/aries_cloudagent/anoncreds/default/legacy_indy/registry.py +++ b/aries_cloudagent/anoncreds/default/legacy_indy/registry.py @@ -1,7 +1,8 @@ """Legacy Indy Registry""" +from asyncio import shield +import json import logging import re -from asyncio import shield from typing import Optional, Pattern, Tuple from urllib.parse import urlparse @@ -19,41 +20,44 @@ IndyLedgerRequestsExecutor, ) from ....multitenant.base import BaseMultitenantManager -from ....revocation.error import RevocationError from ....revocation.anoncreds import AnonCredsRevocation +from ....revocation.error import RevocationError +from ....revocation.models.issuer_cred_rev_record import IssuerCredRevRecord +from ....revocation.recover import generate_ledger_rrrecovery_txn from ....storage.error import StorageNotFoundError +from ...base import ( + AnonCredsObjectAlreadyExists, + AnonCredsObjectNotFound, + AnonCredsRegistrationError, + AnonCredsResolutionError, + AnonCredsSchemaAlreadyExists, + BaseAnonCredsRegistrar, + BaseAnonCredsResolver, +) from ...issuer import AnonCredsIssuer, AnonCredsIssuerError from ...models.anoncreds_cred_def import ( CredDef, + CredDefResult, CredDefState, CredDefValue, - CredDefResult, GetCredDefResult, ) from ...models.anoncreds_revocation import ( - GetRevStatusListResult, AnonCredsRegistryGetRevocationRegistryDefinition, + GetRevStatusListResult, RevRegDef, RevRegDefResult, RevRegDefState, RevStatusList, RevStatusListResult, + RevStatusListState, ) from ...models.anoncreds_schema import ( - GetSchemaResult, AnonCredsSchema, + GetSchemaResult, SchemaResult, SchemaState, ) -from ...base import ( - AnonCredsObjectAlreadyExists, - AnonCredsObjectNotFound, - AnonCredsRegistrationError, - AnonCredsResolutionError, - AnonCredsSchemaAlreadyExists, - BaseAnonCredsRegistrar, - BaseAnonCredsResolver, -) LOGGER = logging.getLogger(__name__) @@ -419,7 +423,6 @@ async def get_revocation_status_list( ) -> GetRevStatusListResult: """Get a revocation list from the registry.""" - # TODO: determine keyword arguments async def register_revocation_status_list( self, profile: Profile, @@ -428,10 +431,10 @@ async def register_revocation_status_list( options: Optional[dict] = None, ) -> RevStatusListResult: """Register a revocation list on the registry.""" + # TODO Handle multitenancy and multi-ledger (like in get cred def) ledger = profile.inject(BaseLedger) - # TODO Translate rev_status_list to rev_reg_entry - rev_reg_entry = {} + rev_reg_entry = {"value": {"accum": rev_status_list.current_accumulator}} try: rev_entry_res = await ledger.send_revoc_reg_entry( @@ -442,7 +445,7 @@ async def register_revocation_status_list( write_ledger=True, endorser_did=None, ) - except LedgerTransactionError as err: # TODO: update errors + except LedgerTransactionError as err: if "InvalidClientRequest" in err.roll_up: # ... if the ledger write fails (with "InvalidClientRequest") # e.g. aries_cloudagent.ledger.error.LedgerTransactionError: @@ -453,6 +456,7 @@ async def register_revocation_status_list( LOGGER.warn(err) (_, _, res) = await self.fix_ledger_entry( profile, + rev_status_list, True, ledger.pool.genesis_txns, ) @@ -463,22 +467,33 @@ async def register_revocation_status_list( # e.g. aries_cloudagent.ledger.error.LedgerTransactionError: # Ledger rejected transaction request: client request invalid: # InvalidClientTaaAcceptanceError(...) - LOGGER.error("Ledger update failed due to TAA issue") - LOGGER.error(err) - raise err + LOGGER.exception("Ledger update failed due to TAA issue") + raise AnonCredsRegistrationError( + "Ledger update failed due to TAA Issue" + ) from err else: # not sure what happened, raise an error - LOGGER.error("Ledger update failed due to unknown issue") - LOGGER.error(err) - raise err + LOGGER.exception("Ledger update failed due to unknown issue") + raise AnonCredsRegistrationError( + "Ledger update failed due to unknown issue" + ) from err - # TODO Fill this in with rev_entry_res and rev_status_list - return RevStatusListResult() + return RevStatusListResult( + job_id=None, + revocation_status_list_state=RevStatusListState( + state=RevStatusListState.STATE_FINISHED, + revocation_status_list=rev_status_list, + ), + registration_metadata={}, + revocation_status_list_metadata={ + "seqNo": rev_entry_res["result"]["txnMetadata"]["seqNo"], + }, + ) - # TODO Adjust this to work here (moved from IssuerRevRegRecord) async def fix_ledger_entry( self, profile: Profile, + rev_status_list: RevStatusList, apply_ledger_update: bool, genesis_transactions: str, ) -> Tuple[dict, dict, dict]: @@ -486,7 +501,9 @@ async def fix_ledger_entry( # get rev reg delta (revocations published to ledger) ledger = profile.inject(BaseLedger) async with ledger: - (rev_reg_delta, _) = await ledger.get_revoc_reg_delta(self.revoc_reg_id) + (rev_reg_delta, _) = await ledger.get_revoc_reg_delta( + rev_status_list.rev_reg_id + ) # get rev reg records from wallet (revocations and status) recs = [] @@ -496,7 +513,7 @@ async def fix_ledger_entry( applied_txn = {} async with profile.session() as session: recs = await IssuerCredRevRecord.query_by_ids( - session, rev_reg_id=self.revoc_reg_id + session, rev_reg_id=rev_status_list.rev_reg_id ) revoked_ids = [] @@ -509,8 +526,8 @@ async def fix_ledger_entry( LOGGER.debug(">>> fixed entry recs count = %s", rec_count) LOGGER.debug( - ">>> rev_reg_record.revoc_reg_entry.value: %s", - self.revoc_reg_entry.value, + ">>> rev_status_list.revocation_list: %s", + rev_status_list.revocation_list, ) LOGGER.debug( '>>> rev_reg_delta.get("value"): %s', rev_reg_delta.get("value") @@ -518,8 +535,11 @@ async def fix_ledger_entry( # if we had any revocation discrepencies, check the accumulator value if rec_count > 0: - if (self.revoc_reg_entry.value and rev_reg_delta.get("value")) and not ( - self.revoc_reg_entry.value.accum == rev_reg_delta["value"]["accum"] + if ( + rev_status_list.current_accumulator and rev_reg_delta.get("value") + ) and ( + rev_status_list.current_accumulator + != rev_reg_delta["value"]["accum"] ): # self.revoc_reg_entry = rev_reg_delta["value"] # await self.save(session) @@ -527,7 +547,7 @@ async def fix_ledger_entry( calculated_txn = await generate_ledger_rrrecovery_txn( genesis_transactions, - self.revoc_reg_id, + rev_status_list.rev_reg_id, revoked_ids, ) recovery_txn = json.loads(calculated_txn.to_json()) @@ -543,7 +563,7 @@ async def fix_ledger_entry( async with ledger: ledger_response = await ledger.send_revoc_reg_entry( - self.revoc_reg_id, "CL_ACCUM", recovery_txn + rev_status_list.rev_reg_id, "CL_ACCUM", recovery_txn ) applied_txn = ledger_response["result"] diff --git a/aries_cloudagent/anoncreds/issuer.py b/aries_cloudagent/anoncreds/issuer.py index 807ee45083..e48541f07b 100644 --- a/aries_cloudagent/anoncreds/issuer.py +++ b/aries_cloudagent/anoncreds/issuer.py @@ -820,6 +820,8 @@ async def revoke_credentials( """ + # TODO This method should return the old status list, the new status list, + # and the list of changed indices delta = None failed_crids = set() max_attempt = 5 diff --git a/aries_cloudagent/anoncreds/models/anoncreds_revocation.py b/aries_cloudagent/anoncreds/models/anoncreds_revocation.py index 66a93eac02..8fad153730 100644 --- a/aries_cloudagent/anoncreds/models/anoncreds_revocation.py +++ b/aries_cloudagent/anoncreds/models/anoncreds_revocation.py @@ -345,11 +345,9 @@ class Meta: def __init__( self, state: str, - revocation_status_list_id: str, revocation_status_list: RevStatusList, ): self.state = state - self.revocation_status_list_id = revocation_status_list_id self.revocation_status_list = revocation_status_list @@ -372,11 +370,8 @@ class Meta: ] ) ) - revocation_status_list_id = fields.Str( - description="revocation registry definition id" - ) revocation_status_list = fields.Nested( - RevStatusListSchema(), description="revocation registry definition" + RevStatusListSchema(), description="revocation list" ) diff --git a/aries_cloudagent/anoncreds/registry.py b/aries_cloudagent/anoncreds/registry.py index 4297532eda..5f352c1280 100644 --- a/aries_cloudagent/anoncreds/registry.py +++ b/aries_cloudagent/anoncreds/registry.py @@ -152,11 +152,12 @@ async def get_revocation_status_list( async def register_revocation_status_list( self, profile: Profile, + rev_reg_def: RevRegDef, rev_status_list: RevStatusList, options: Optional[dict] = None, ) -> RevStatusListResult: """Register a revocation list on the registry.""" registrar = await self._registrar_for_identifier("something") return await registrar.register_revocation_status_list( - profile, rev_status_list, options + profile, rev_reg_def, rev_status_list, options ) diff --git a/aries_cloudagent/revocation/models/issuer_rev_reg_record.py b/aries_cloudagent/revocation/models/issuer_rev_reg_record.py index 42fb300521..5fc1d7f5c5 100644 --- a/aries_cloudagent/revocation/models/issuer_rev_reg_record.py +++ b/aries_cloudagent/revocation/models/issuer_rev_reg_record.py @@ -1,20 +1,17 @@ """Issuer revocation registry storage handling.""" +from functools import total_ordering import json import logging -import uuid -from functools import total_ordering from os.path import join from pathlib import Path from shutil import move -from typing import Any, Mapping, Sequence, Union, Tuple, Optional +from typing import Any, Mapping, Optional, Sequence, Tuple, Union from urllib.parse import urlparse +import uuid from marshmallow import fields, validate -from ...anoncreds.registry import AnonCredsRegistry - -from ...core.profile import Profile, ProfileSession from ...anoncreds.issuer import AnonCredsIssuer, AnonCredsIssuerError from ...anoncreds.models.anoncreds_revocation import ( RevRegDef, @@ -23,19 +20,16 @@ RevStatusListResult, RevStatusListSchema, ) +from ...anoncreds.registry import AnonCredsRegistry from ...anoncreds.util import indy_client_dir +from ...core.profile import Profile, ProfileSession from ...ledger.base import BaseLedger -from ...ledger.error import LedgerError, LedgerTransactionError +from ...ledger.error import LedgerError from ...messaging.models.base_record import BaseRecord, BaseRecordSchema -from ...messaging.valid import ( - BASE58_SHA256_HASH, - UUIDFour, -) +from ...messaging.valid import BASE58_SHA256_HASH, UUIDFour from ...tails.base import BaseTailsServer - from ..error import RevocationError from ..recover import generate_ledger_rrrecovery_txn - from .issuer_cred_rev_record import IssuerCredRevRecord from .revocation_registry import RevocationRegistry @@ -322,8 +316,8 @@ async def send_entry( self, profile: Profile, options: Optional[dict] = None, - write_ledger: bool = True, - endorser_did: str = None, + write_ledger: bool = True, # TODO Delete me + endorser_did: str = None, # TODO Delete me ) -> RevStatusListResult: """Send a registry entry to the ledger.""" if not ( @@ -346,54 +340,19 @@ async def send_entry( self.revoc_reg_id, self.state ) ) + anoncreds_registry = profile.inject(AnonCredsRegistry) + rev_entry_res = await anoncreds_registry.register_revocation_status_list( + profile, + self.revoc_reg_def, + self.rev_status_list, + options, + ) - try: - rev_entry_res = await anoncreds_registry.register_revocation_status_list( - profile, - self.rev_status_list, # TODO: is this the correct rev_reg_def? - options, - ) - """rev_entry_res = await ledger.send_revoc_reg_entry( - self.revoc_reg_id, - self.revoc_def_type, - self._revoc_reg_entry.ser, - self.issuer_id, - write_ledger=write_ledger, - endorser_did=endorser_did, - )""" - except LedgerTransactionError as err: # TODO: update errors - if "InvalidClientRequest" in err.roll_up: - # ... if the ledger write fails (with "InvalidClientRequest") - # e.g. aries_cloudagent.ledger.error.LedgerTransactionError: - # Ledger rejected transaction request: client request invalid: - # InvalidClientRequest(...) - # In this scenario we try to post a correction - LOGGER.warn("Retry ledger update/fix due to error") - LOGGER.warn(err) - """(_, _, res) = await self.fix_ledger_entry( - profile, - True, - ledger.pool.genesis_txns, - ) - rev_entry_res = {"result": res}""" - rev_entry_res = {"result": "res"} - LOGGER.warn("Ledger update/fix applied") - elif "InvalidClientTaaAcceptanceError" in err.roll_up: - # if no write access (with "InvalidClientTaaAcceptanceError") - # e.g. aries_cloudagent.ledger.error.LedgerTransactionError: - # Ledger rejected transaction request: client request invalid: - # InvalidClientTaaAcceptanceError(...) - LOGGER.error("Ledger update failed due to TAA issue") - LOGGER.error(err) - raise err - else: - # not sure what happened, raise an error - LOGGER.error("Ledger update failed due to unknown issue") - LOGGER.error(err) - raise err if self.state == IssuerRevRegRecord.STATE_POSTED: - self.state = IssuerRevRegRecord.STATE_ACTIVE # initial entry activates + self.state = ( + IssuerRevRegRecord.STATE_ACTIVE + ) # registering rev status list activates async with profile.session() as session: await self.save( session, reason="Published initial revocation registry entry" From d68b805a8c4335d34d40c820fca19e1aa69ed301 Mon Sep 17 00:00:00 2001 From: Adam Burdett Date: Wed, 5 Apr 2023 14:43:34 -0600 Subject: [PATCH 105/150] fix(anoncreds): legacy regex matching Signed-off-by: Adam Burdett --- .../anoncreds/default/__init__.py | 0 .../anoncreds/default/did_web/registry.py | 4 +- .../anoncreds/default/legacy_indy/registry.py | 22 +++++++- .../default/legacy_indy/tests/__init__.py | 0 .../legacy_indy/tests/test_registry.py | 54 +++++++++++++++++++ aries_cloudagent/ledger/indy.py | 2 +- .../tests/test_manager_provider.py | 4 +- aries_cloudagent/ledger/tests/test_indy.py | 2 +- .../decorators/tests/test_attach_decorator.py | 2 +- .../revocation/tests/test_indy.py | 3 +- aries_cloudagent/storage/indy.py | 2 +- .../storage/tests/test_indy_storage.py | 3 +- .../vc_holder/tests/test_indy_vc_holder.py | 2 +- aries_cloudagent/wallet/indy.py | 5 +- .../wallet/tests/test_indy_wallet.py | 6 +-- 15 files changed, 94 insertions(+), 17 deletions(-) create mode 100644 aries_cloudagent/anoncreds/default/__init__.py create mode 100644 aries_cloudagent/anoncreds/default/legacy_indy/tests/__init__.py create mode 100644 aries_cloudagent/anoncreds/default/legacy_indy/tests/test_registry.py diff --git a/aries_cloudagent/anoncreds/default/__init__.py b/aries_cloudagent/anoncreds/default/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aries_cloudagent/anoncreds/default/did_web/registry.py b/aries_cloudagent/anoncreds/default/did_web/registry.py index af2089c428..76581c3167 100644 --- a/aries_cloudagent/anoncreds/default/did_web/registry.py +++ b/aries_cloudagent/anoncreds/default/did_web/registry.py @@ -19,7 +19,9 @@ class DIDWebRegistry(BaseAnonCredsResolver, BaseAnonCredsRegistrar): """DIDWebRegistry""" def __init__(self): - self._supported_identifiers_regex = re.compile(r"^did:web:.*$") + self._supported_identifiers_regex = re.compile( + r"^(did:web:)([a-zA-Z0-9%._-]*:)*[a-zA-Z0-9%._-]+$" + ) @property def supported_identifiers_regex(self) -> Pattern: diff --git a/aries_cloudagent/anoncreds/default/legacy_indy/registry.py b/aries_cloudagent/anoncreds/default/legacy_indy/registry.py index 577224a094..4c7a1f1927 100644 --- a/aries_cloudagent/anoncreds/default/legacy_indy/registry.py +++ b/aries_cloudagent/anoncreds/default/legacy_indy/registry.py @@ -58,6 +58,7 @@ SchemaResult, SchemaState, ) +from base58 import alphabet LOGGER = logging.getLogger(__name__) @@ -69,8 +70,25 @@ class LegacyIndyRegistry(BaseAnonCredsResolver, BaseAnonCredsRegistrar): """LegacyIndyRegistry""" def __init__(self): - self._supported_identifiers_regex = re.compile(r"^(?!did).*$") - # TODO: fix regex (too general) + B58 = alphabet if isinstance(alphabet, str) else alphabet.decode("ascii") + INDY_DID = rf"^(did:sov:)?[{B58}]{{21,22}}$" + INDY_SCHEMA_ID = rf"^[{B58}]{{21,22}}:2:.+:[0-9.]+$" + INDY_CRED_DEF_ID = ( + rf"^([{B58}]{{21,22}})" # issuer DID + f":3" # cred def id marker + f":CL" # sig alg + rf":(([1-9][0-9]*)|([{B58}]{{21,22}}:2:.+:[0-9.]+))" # schema txn / id + f":(.+)?$" # tag + ) + INDY_REV_REG_DEF_ID = ( + rf"^([{B58}]{{21,22}}):4:" + rf"([{B58}]{{21,22}}):3:" + rf"CL:(([1-9][0-9]*)|([{B58}]{{21,22}}:2:.+:[0-9.]+))(:.+)?:" + rf"CL_ACCUM:(.+$)" + ) + self._supported_identifiers_regex = re.compile( + rf"{INDY_DID}|{INDY_SCHEMA_ID}|{INDY_CRED_DEF_ID}|{INDY_REV_REG_DEF_ID}" + ) @property def supported_identifiers_regex(self) -> Pattern: diff --git a/aries_cloudagent/anoncreds/default/legacy_indy/tests/__init__.py b/aries_cloudagent/anoncreds/default/legacy_indy/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aries_cloudagent/anoncreds/default/legacy_indy/tests/test_registry.py b/aries_cloudagent/anoncreds/default/legacy_indy/tests/test_registry.py new file mode 100644 index 0000000000..a97f485a34 --- /dev/null +++ b/aries_cloudagent/anoncreds/default/legacy_indy/tests/test_registry.py @@ -0,0 +1,54 @@ +"""Test LegacyIndyRegistry.""" + +import pytest +import re +from ..registry import LegacyIndyRegistry +from base58 import alphabet + +B58 = alphabet if isinstance(alphabet, str) else alphabet.decode("ascii") +INDY_DID = rf"^(did:sov:)?[{B58}]{{21,22}}$" +INDY_SCHEMA_ID = rf"^[{B58}]{{21,22}}:2:.+:[0-9.]+$" +INDY_CRED_DEF_ID = ( + rf"^([{B58}]{{21,22}})" # issuer DID + f":3" # cred def id marker + f":CL" # sig alg + rf":(([1-9][0-9]*)|([{B58}]{{21,22}}:2:.+:[0-9.]+))" # schema txn / id + f":(.+)?$" # tag +) +INDY_REV_REG_DEF_ID = ( + rf"^([{B58}]{{21,22}}):4:" + rf"([{B58}]{{21,22}}):3:" + rf"CL:(([1-9][0-9]*)|([{B58}]{{21,22}}:2:.+:[0-9.]+))(:.+)?:" + rf"CL_ACCUM:(.+$)" +) +SUPPORTED_ID_REGEX = re.compile( + rf"{INDY_DID}|{INDY_SCHEMA_ID}|{INDY_CRED_DEF_ID}|{INDY_REV_REG_DEF_ID}" +) + +TEST_INDY_DID = "WgWxqztrNooG92RXvxSTWv" +TEST_INDY_DID_1 = "did:sov:WgWxqztrNooG92RXvxSTWv" +TEST_INDY_SCHEMA_ID = "WgWxqztrNooG92RXvxSTWv:2:schema_name:1.0" +TEST_INDY_CRED_DEF_ID = "WgWxqztrNooG92RXvxSTWv:3:CL:20:tag" +TEST_INDY_REV_REG_DEF_ID = ( + "WgWxqztrNooG92RXvxSTWv:4:WgWxqztrNooG92RXvxSTWv:3:CL:20:tag:CL_ACCUM:0" +) + + +@pytest.fixture +def registry(): + """Registry fixture""" + yield LegacyIndyRegistry() + + +class TestLegacyIndyRegistry: + @pytest.mark.asyncio + async def test_supported_did_regex(self, registry: LegacyIndyRegistry): + """Test the supported_did_regex.""" + + assert registry.supported_identifiers_regex == SUPPORTED_ID_REGEX + assert bool(registry.supported_identifiers_regex.match(TEST_INDY_DID)) + assert bool(registry.supported_identifiers_regex.match(TEST_INDY_DID_1)) + assert bool(registry.supported_identifiers_regex.match(TEST_INDY_SCHEMA_ID)) + assert bool( + registry.supported_identifiers_regex.match(TEST_INDY_REV_REG_DEF_ID) + ) diff --git a/aries_cloudagent/ledger/indy.py b/aries_cloudagent/ledger/indy.py index 48a471a7ce..068a63e0cf 100644 --- a/aries_cloudagent/ledger/indy.py +++ b/aries_cloudagent/ledger/indy.py @@ -16,7 +16,7 @@ from ..cache.base import BaseCache from ..config.base import BaseInjector, BaseProvider, BaseSettings -from ..anoncreds.sdk.error import IndyErrorHandler +from ..indy.sdk.error import IndyErrorHandler from ..storage.base import StorageRecord from ..storage.indy import IndySdkStorage from ..utils import sentinel diff --git a/aries_cloudagent/ledger/multiple_ledger/tests/test_manager_provider.py b/aries_cloudagent/ledger/multiple_ledger/tests/test_manager_provider.py index d4c2854d38..14c75c514b 100644 --- a/aries_cloudagent/ledger/multiple_ledger/tests/test_manager_provider.py +++ b/aries_cloudagent/ledger/multiple_ledger/tests/test_manager_provider.py @@ -5,8 +5,8 @@ from ....askar.profile import AskarProfileManager from ....config.injection_context import InjectionContext from ....core.in_memory import InMemoryProfile -from ....anoncreds.sdk.profile import IndySdkProfile -from ....anoncreds.sdk.wallet_setup import IndyOpenWallet, IndyWalletConfig +from ....indy.sdk.profile import IndySdkProfile +from ....indy.sdk.wallet_setup import IndyOpenWallet, IndyWalletConfig from ....ledger.base import BaseLedger from ....ledger.indy import IndySdkLedgerPool, IndySdkLedger diff --git a/aries_cloudagent/ledger/tests/test_indy.py b/aries_cloudagent/ledger/tests/test_indy.py index 61c5d27b87..bffbdc1fc1 100644 --- a/aries_cloudagent/ledger/tests/test_indy.py +++ b/aries_cloudagent/ledger/tests/test_indy.py @@ -10,7 +10,7 @@ from ...config.injection_context import InjectionContext from ...cache.in_memory import InMemoryCache from ...anoncreds.issuer import AnonCredsIssuer, AnonCredsIssuerError -from ...anoncreds.sdk.profile import IndySdkProfile +from ...indy.sdk.profile import IndySdkProfile from ...storage.record import StorageRecord from ...wallet.base import BaseWallet from ...wallet.did_info import DIDInfo diff --git a/aries_cloudagent/messaging/decorators/tests/test_attach_decorator.py b/aries_cloudagent/messaging/decorators/tests/test_attach_decorator.py index 72c1edc8e3..c0f1f26169 100644 --- a/aries_cloudagent/messaging/decorators/tests/test_attach_decorator.py +++ b/aries_cloudagent/messaging/decorators/tests/test_attach_decorator.py @@ -6,7 +6,7 @@ import pytest -from ....anoncreds.sdk.wallet_setup import IndyWalletConfig +from ....indy.sdk.wallet_setup import IndyWalletConfig from ....messaging.models.base import BaseModelError from ....wallet.did_method import SOV from ....wallet.indy import IndySdkWallet diff --git a/aries_cloudagent/revocation/tests/test_indy.py b/aries_cloudagent/revocation/tests/test_indy.py index 4e477d6f25..6128fbf8b1 100644 --- a/aries_cloudagent/revocation/tests/test_indy.py +++ b/aries_cloudagent/revocation/tests/test_indy.py @@ -2,6 +2,8 @@ from asynctest import mock as async_mock, TestCase as AsyncTestCase +from ...revocation.anoncreds import AnonCredsRevocation + from ...core.in_memory import InMemoryProfile from ...ledger.base import BaseLedger from ...ledger.multiple_ledger.ledger_requests_executor import ( @@ -15,7 +17,6 @@ RevocationNotSupportedError, RevocationRegistryBadSizeError, ) -from ..indy import AnonCredsRevocation from ..models.issuer_rev_reg_record import DEFAULT_REGISTRY_SIZE, IssuerRevRegRecord from ..models.revocation_registry import RevocationRegistry diff --git a/aries_cloudagent/storage/indy.py b/aries_cloudagent/storage/indy.py index 9413ab146b..9a5e37aec8 100644 --- a/aries_cloudagent/storage/indy.py +++ b/aries_cloudagent/storage/indy.py @@ -22,7 +22,7 @@ StorageSearchError, ) from .record import StorageRecord -from ..anoncreds.sdk.wallet_setup import IndyOpenWallet +from ..indy.sdk.wallet_setup import IndyOpenWallet LOGGER = logging.getLogger(__name__) diff --git a/aries_cloudagent/storage/tests/test_indy_storage.py b/aries_cloudagent/storage/tests/test_indy_storage.py index 7b836571e5..dd06325849 100644 --- a/aries_cloudagent/storage/tests/test_indy_storage.py +++ b/aries_cloudagent/storage/tests/test_indy_storage.py @@ -11,8 +11,9 @@ from indy.error import ErrorCode from asynctest import mock as async_mock +from ...indy.sdk.profile import IndySdkProfileManager, IndySdkProfile + from ...config.injection_context import InjectionContext -from ...anoncreds.sdk.profile import IndySdkProfileManager, IndySdkProfile from ...storage.base import BaseStorage from ...storage.error import StorageError, StorageSearchError from ...storage.indy import IndySdkStorage diff --git a/aries_cloudagent/storage/vc_holder/tests/test_indy_vc_holder.py b/aries_cloudagent/storage/vc_holder/tests/test_indy_vc_holder.py index 5990b297da..fa092511b4 100644 --- a/aries_cloudagent/storage/vc_holder/tests/test_indy_vc_holder.py +++ b/aries_cloudagent/storage/vc_holder/tests/test_indy_vc_holder.py @@ -3,7 +3,7 @@ from ....config.injection_context import InjectionContext -from ....anoncreds.sdk.profile import IndySdkProfileManager, IndySdkProfile +from ....indy.sdk.profile import IndySdkProfileManager, IndySdkProfile from ....ledger.indy import IndySdkLedgerPool from ....wallet.indy import IndySdkWallet diff --git a/aries_cloudagent/wallet/indy.py b/aries_cloudagent/wallet/indy.py index 5d8d2f7e6b..965fbd37ee 100644 --- a/aries_cloudagent/wallet/indy.py +++ b/aries_cloudagent/wallet/indy.py @@ -12,9 +12,10 @@ from indy.error import IndyError, ErrorCode +from ..indy.sdk.error import IndyErrorHandler + from ..did.did_key import DIDKey -from ..anoncreds.sdk.error import IndyErrorHandler -from ..anoncreds.sdk.wallet_setup import IndyOpenWallet +from ..indy.sdk.wallet_setup import IndyOpenWallet from ..ledger.base import BaseLedger from ..ledger.endpoint_type import EndpointType from ..ledger.error import LedgerConfigError diff --git a/aries_cloudagent/wallet/tests/test_indy_wallet.py b/aries_cloudagent/wallet/tests/test_indy_wallet.py index 107e8c5656..47ae72cdec 100644 --- a/aries_cloudagent/wallet/tests/test_indy_wallet.py +++ b/aries_cloudagent/wallet/tests/test_indy_wallet.py @@ -12,9 +12,9 @@ from ...config.injection_context import InjectionContext from ...core.error import ProfileDuplicateError, ProfileError, ProfileNotFoundError from ...core.in_memory import InMemoryProfile -from ...anoncreds.sdk import wallet_setup as test_setup_module -from ...anoncreds.sdk.profile import IndySdkProfile, IndySdkProfileManager -from ...anoncreds.sdk.wallet_setup import IndyWalletConfig +from ...indy.sdk import wallet_setup as test_setup_module +from ...indy.sdk.profile import IndySdkProfile, IndySdkProfileManager +from ...indy.sdk.wallet_setup import IndyWalletConfig from ...ledger.endpoint_type import EndpointType from ...ledger.indy import IndySdkLedgerPool from ...wallet.did_method import SOV, DIDMethods From f233098f5c5ebd65cea3864259cca86df8c5bdf5 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Wed, 5 Apr 2023 21:26:54 -0400 Subject: [PATCH 106/150] feat: revoke_credentials returns rev lists Signed-off-by: Daniel Bluhm --- anoncreds_test.py | 2 +- aries_cloudagent/anoncreds/base.py | 11 ++ .../anoncreds/default/legacy_indy/registry.py | 73 ++++++++++-- aries_cloudagent/anoncreds/issuer.py | 46 +++++--- aries_cloudagent/anoncreds/registry.py | 16 ++- aries_cloudagent/anoncreds/routes.py | 108 +++++++++--------- docker-compose.yml | 21 ++++ 7 files changed, 195 insertions(+), 82 deletions(-) diff --git a/anoncreds_test.py b/anoncreds_test.py index 4bfe8ec99a..8417503501 100644 --- a/anoncreds_test.py +++ b/anoncreds_test.py @@ -45,7 +45,7 @@ async def main(): "issuerId": public_did.did, }, "options": { - "support_revocation": False, + "support_revocation": True, }, }, ) diff --git a/aries_cloudagent/anoncreds/base.py b/aries_cloudagent/anoncreds/base.py index eb0ca72a6c..f619cea729 100644 --- a/aries_cloudagent/anoncreds/base.py +++ b/aries_cloudagent/anoncreds/base.py @@ -154,3 +154,14 @@ async def register_revocation_status_list( options: Optional[dict] = None, ) -> RevStatusListResult: """Register a revocation list on the registry.""" + + @abstractmethod + async def update_revocation_status_list( + self, + profile: Profile, + rev_reg_def: RevRegDef, + prev_status_list: RevStatusList, + curr_status_list: RevStatusList, + options: Optional[dict] = None, + ) -> RevStatusListResult: + """Update a revocation list on the registry.""" diff --git a/aries_cloudagent/anoncreds/default/legacy_indy/registry.py b/aries_cloudagent/anoncreds/default/legacy_indy/registry.py index 577224a094..d0e3507987 100644 --- a/aries_cloudagent/anoncreds/default/legacy_indy/registry.py +++ b/aries_cloudagent/anoncreds/default/legacy_indy/registry.py @@ -423,24 +423,22 @@ async def get_revocation_status_list( ) -> GetRevStatusListResult: """Get a revocation list from the registry.""" - async def register_revocation_status_list( + async def _revoc_reg_entry_with_fix( self, profile: Profile, - rev_reg_def: RevRegDef, rev_status_list: RevStatusList, - options: Optional[dict] = None, - ) -> RevStatusListResult: - """Register a revocation list on the registry.""" + rev_reg_def_type: str, + entry: dict, + ) -> dict: + """Send a revocation registry entry to the ledger with fixes if needed""" # TODO Handle multitenancy and multi-ledger (like in get cred def) ledger = profile.inject(BaseLedger) - rev_reg_entry = {"value": {"accum": rev_status_list.current_accumulator}} - try: rev_entry_res = await ledger.send_revoc_reg_entry( rev_status_list.rev_reg_id, - rev_reg_def.type, - rev_reg_entry, + rev_reg_def_type, + entry, rev_status_list.issuer_id, write_ledger=True, endorser_did=None, @@ -478,6 +476,22 @@ async def register_revocation_status_list( "Ledger update failed due to unknown issue" ) from err + return rev_entry_res + + async def register_revocation_status_list( + self, + profile: Profile, + rev_reg_def: RevRegDef, + rev_status_list: RevStatusList, + options: Optional[dict] = None, + ) -> RevStatusListResult: + """Register a revocation list on the registry.""" + rev_reg_entry = {"value": {"accum": rev_status_list.current_accumulator}} + + rev_entry_res = await self._revoc_reg_entry_with_fix( + profile, rev_status_list, rev_reg_def.type, rev_reg_entry + ) + return RevStatusListResult( job_id=None, revocation_status_list_state=RevStatusListState( @@ -490,6 +504,47 @@ async def register_revocation_status_list( }, ) + async def update_revocation_status_list( + self, + profile: Profile, + rev_reg_def: RevRegDef, + prev_status_list: RevStatusList, + curr_status_list: RevStatusList, + options: Optional[dict] = None, + ) -> RevStatusListResult: + """Update a revocation status list.""" + newly_revoked_indices = [ + # Remember: Indices in Indy are 1-based + index + 1 + for index, (prev, curr) in enumerate( + zip(prev_status_list.revocation_list, curr_status_list.revocation_list) + ) + if prev != curr + ] + rev_reg_entry = { + "value": { + "accum": curr_status_list.current_accumulator, + "prevAccum": prev_status_list.current_accumulator, + "revoked": newly_revoked_indices, + } + } + + rev_entry_res = await self._revoc_reg_entry_with_fix( + profile, curr_status_list, rev_reg_def.type, rev_reg_entry + ) + + return RevStatusListResult( + job_id=None, + revocation_status_list_state=RevStatusListState( + state=RevStatusListState.STATE_FINISHED, + revocation_status_list=curr_status_list, + ), + registration_metadata={}, + revocation_status_list_metadata={ + "seqNo": rev_entry_res["result"]["txnMetadata"]["seqNo"], + }, + ) + async def fix_ledger_entry( self, profile: Profile, diff --git a/aries_cloudagent/anoncreds/issuer.py b/aries_cloudagent/anoncreds/issuer.py index e48541f07b..f0fb4a2eb7 100644 --- a/aries_cloudagent/anoncreds/issuer.py +++ b/aries_cloudagent/anoncreds/issuer.py @@ -3,7 +3,7 @@ import asyncio import logging from time import time -from typing import Optional, Sequence, Tuple +from typing import NamedTuple, Optional, Sequence, Tuple from anoncreds import ( AnoncredsError, @@ -54,6 +54,12 @@ class AnonCredsIssuerRevocationRegistryFullError(AnonCredsIssuerError): """Revocation registry is full when issuing a new credential.""" +class RevokeResult(NamedTuple): + prev: Optional[RevocationStatusList] = None + curr: Optional[RevocationStatusList] = None + failed: Optional[Sequence[str]] = None + + class AnonCredsIssuer: """AnonCreds issuer class.""" @@ -806,7 +812,7 @@ async def revoke_credentials( revoc_reg_id: str, tails_file_path: str, cred_revoc_ids: Sequence[str], - ) -> Tuple[str, Sequence[str]]: + ) -> RevokeResult: """ Revoke a set of credentials in a revocation registry. @@ -816,13 +822,14 @@ async def revoke_credentials( cred_revoc_ids: sequences of credential indexes in the revocation registry Returns: - Tuple with the combined revocation delta, list of cred rev ids not revoked + Tuple with the update revocation status list, list of cred rev ids not revoked """ # TODO This method should return the old status list, the new status list, # and the list of changed indices - delta = None + prev_status_list = None + updated_list = None failed_crids = set() max_attempt = 5 attempt = 0 @@ -835,20 +842,20 @@ async def revoke_credentials( ) try: async with self._profile.session() as session: - rev_reg_def = await session.handle.fetch( + rev_reg_def_entry = await session.handle.fetch( CATEGORY_REV_REG_DEF, revoc_reg_id ) - rev_status_list = await session.handle.fetch( + rev_status_list_entry = await session.handle.fetch( CATEGORY_REV_STATUS_LIST, revoc_reg_id ) rev_reg_info = await session.handle.fetch( CATEGORY_REV_REG_INFO, revoc_reg_id ) - if not rev_reg_def: + if not rev_reg_def_entry: raise AnonCredsIssuerError( "Revocation registry definition not found" ) - if not rev_status_list: + if not rev_status_list_entry: raise AnonCredsIssuerError("Revocation registry not found") if not rev_reg_info: raise AnonCredsIssuerError("Revocation registry metadata not found") @@ -858,7 +865,9 @@ async def revoke_credentials( ) from err try: - rev_reg_def = RevocationRegistryDefinition.load(rev_reg_def.raw_value) + rev_reg_def = RevocationRegistryDefinition.load( + rev_reg_def_entry.raw_value + ) except AnoncredsError as err: raise AnonCredsIssuerError( "Error loading revocation registry definition" @@ -903,14 +912,16 @@ async def revoke_credentials( break try: - rev_status_list = RevocationStatusList.load(rev_status_list.raw_value) + prev_status_list = RevocationStatusList.load( + rev_status_list_entry.raw_value + ) except AnoncredsError as err: raise AnonCredsIssuerError("Error loading revocation registry") from err try: - delta = await asyncio.get_event_loop().run_in_executor( + updated_list = await asyncio.get_event_loop().run_in_executor( None, - lambda: rev_status_list.update( + lambda: prev_status_list.update( int(time.time()), None, # issued list(rev_crids), # revoked @@ -935,7 +946,7 @@ async def revoke_credentials( "Revocation registry missing, skipping update: {}", revoc_reg_id, ) - delta = None + updated_list = None break rev_info_upd = rev_info_upd.value_json if rev_info_upd != rev_info: @@ -944,7 +955,7 @@ async def revoke_credentials( await txn.handle.replace( CATEGORY_REV_STATUS_LIST, revoc_reg_id, - rev_status_list.to_json_buffer(), + updated_list.to_json_buffer(), ) used_ids.update(rev_crids) rev_info_upd["used_ids"] = sorted(used_ids) @@ -956,9 +967,10 @@ async def revoke_credentials( raise AnonCredsIssuerError("Error saving revocation registry") from err break - return ( - delta and delta.to_json(), - [str(rev_id) for rev_id in sorted(failed_crids)], + return RevokeResult( + prev=prev_status_list, + curr=updated_list, + failed=[str(rev_id) for rev_id in sorted(failed_crids)], ) async def merge_revocation_registry_deltas( diff --git a/aries_cloudagent/anoncreds/registry.py b/aries_cloudagent/anoncreds/registry.py index 5f352c1280..55383372a6 100644 --- a/aries_cloudagent/anoncreds/registry.py +++ b/aries_cloudagent/anoncreds/registry.py @@ -157,7 +157,21 @@ async def register_revocation_status_list( options: Optional[dict] = None, ) -> RevStatusListResult: """Register a revocation list on the registry.""" - registrar = await self._registrar_for_identifier("something") + registrar = await self._registrar_for_identifier(rev_status_list.issuer_id) return await registrar.register_revocation_status_list( profile, rev_reg_def, rev_status_list, options ) + + async def update_revocation_status_list( + self, + profile: Profile, + rev_reg_def: RevRegDef, + prev_status_list: RevStatusList, + curr_status_list: RevStatusList, + options: Optional[dict] = None, + ) -> RevStatusListResult: + """Update a revocation list on the registry.""" + registrar = await self._registrar_for_identifier(curr_status_list.issuer_id) + return await registrar.update_revocation_status_list( + profile, rev_reg_def, prev_status_list, curr_status_list, options + ) diff --git a/aries_cloudagent/anoncreds/routes.py b/aries_cloudagent/anoncreds/routes.py index c57721a557..5a52a93c2b 100644 --- a/aries_cloudagent/anoncreds/routes.py +++ b/aries_cloudagent/anoncreds/routes.py @@ -260,6 +260,60 @@ async def cred_def_post(request: web.BaseRequest): return web.json_response(result.serialize()) +@docs(tags=["anoncreds"], summary="") +@match_info_schema(CredIdMatchInfo()) +@response_schema(GetCredDefResultSchema(), 200, description="") +async def cred_def_get(request: web.BaseRequest): + """Request handler for getting credential definition. + + Args: + + Returns: + + """ + context: AdminRequestContext = request["context"] + anon_creds_registry = context.inject(AnonCredsRegistry) + credential_id = request.match_info["cred_def_id"] + result = await anon_creds_registry.get_credential_definition( + context.profile, credential_id + ) + return web.json_response(result.serialize()) + + +class GetCredDefsResponseSchema(OpenAPISchema): + """AnonCredsRegistryGetCredDefsSchema""" + + credential_definition_ids = fields.List( + fields.Str( + description="credential definition identifiers", + ) + ) + + +@docs(tags=["anoncreds"], summary="") +@querystring_schema(CredDefsQueryStringSchema()) +@response_schema(GetCredDefsResponseSchema(), 200, description="") +async def cred_defs_get(request: web.BaseRequest): + """Request handler for getting all credential definitions. + + Args: + + Returns: + + """ + context: AdminRequestContext = request["context"] + issuer = AnonCredsIssuer(context.profile) + + cred_def_ids = await issuer.get_created_credential_definitions( + issuer_id=request.query.get("issuer_id"), + schema_id=request.query.get("schema_id"), + schema_name=request.query.get("schema_name"), + schema_version=request.query.get("schema_version"), + state=request.query.get("state"), + ) + return web.json_response(cred_def_ids) + + class RevRegCreateRequestSchema(OpenAPISchema): """Request schema for revocation registry creation request.""" @@ -333,60 +387,6 @@ async def rev_reg_def_post(request: web.BaseRequest): return web.json_response(result.serialize()) -@docs(tags=["anoncreds"], summary="") -@match_info_schema(CredIdMatchInfo()) -@response_schema(GetCredDefResultSchema(), 200, description="") -async def cred_def_get(request: web.BaseRequest): - """Request handler for getting credential definition. - - Args: - - Returns: - - """ - context: AdminRequestContext = request["context"] - anon_creds_registry = context.inject(AnonCredsRegistry) - credential_id = request.match_info["cred_def_id"] - result = await anon_creds_registry.get_credential_definition( - context.profile, credential_id - ) - return web.json_response(result.serialize()) - - -class GetCredDefsResponseSchema(OpenAPISchema): - """AnonCredsRegistryGetCredDefsSchema""" - - credential_definition_ids = fields.List( - fields.Str( - description="credential definition identifiers", - ) - ) - - -@docs(tags=["anoncreds"], summary="") -@querystring_schema(CredDefsQueryStringSchema()) -@response_schema(GetCredDefsResponseSchema(), 200, description="") -async def cred_defs_get(request: web.BaseRequest): - """Request handler for getting all credential definitions. - - Args: - - Returns: - - """ - context: AdminRequestContext = request["context"] - issuer = AnonCredsIssuer(context.profile) - - cred_def_ids = await issuer.get_created_credential_definitions( - issuer_id=request.query.get("issuer_id"), - schema_id=request.query.get("schema_id"), - schema_name=request.query.get("schema_name"), - schema_version=request.query.get("schema_version"), - state=request.query.get("state"), - ) - return web.json_response(cred_def_ids) - - async def register(app: web.Application): """Register routes.""" diff --git a/docker-compose.yml b/docker-compose.yml index 603b37d814..acbbb3edc4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -19,6 +19,7 @@ services: --wallet-key test --auto-provision --genesis-url https://raw.githubusercontent.com/Indicio-tech/indicio-network/main/genesis_files/pool_transactions_testnet_genesis + --tails-server-base-url http://tails:6543 --log-level debug healthcheck: test: ["CMD-SHELL", "python", "healthcheck.py", "http://localhost:3001/status/live"] @@ -26,6 +27,9 @@ services: interval: 3s timeout: 5s retries: 5 + depends_on: + tails: + condition: service_started bob: platform: linux/amd64 @@ -43,6 +47,7 @@ services: --wallet-key test --auto-provision --genesis-url https://raw.githubusercontent.com/Indicio-tech/indicio-network/main/genesis_files/pool_transactions_testnet_genesis + --tails-server-base-url http://tails:6543 --log-level debug healthcheck: test: ["CMD-SHELL", "python", "healthcheck.py", "http://localhost:3005/status/live"] @@ -50,7 +55,23 @@ services: interval: 3s timeout: 5s retries: 5 + depends_on: + tails: + condition: service_started + tails: + platform: linux/amd64 + image: ghcr.io/bcgov/tails-server:latest + ports: + - 6543:6543 + environment: + - GENESIS_URL=https://raw.githubusercontent.com/Indicio-tech/indicio-network/main/genesis_files/pool_transactions_testnet_genesis + command: > + tails-server + --host 0.0.0.0 + --port 6543 + --storage-path /tmp/tails-files + --log-level INFO tests: platform: linux/amd64 From c6904050a1f213279a8ff818e24cde7bba6660b6 Mon Sep 17 00:00:00 2001 From: Adam Burdett Date: Thu, 6 Apr 2023 10:59:49 -0600 Subject: [PATCH 107/150] fix: raise not imp Signed-off-by: Adam Burdett --- .../anoncreds/default/did_indy/registry.py | 25 ++++++++++++------- .../anoncreds/default/did_web/registry.py | 13 +++++++--- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/aries_cloudagent/anoncreds/default/did_indy/registry.py b/aries_cloudagent/anoncreds/default/did_indy/registry.py index 4e771d9685..67564bb035 100644 --- a/aries_cloudagent/anoncreds/default/did_indy/registry.py +++ b/aries_cloudagent/anoncreds/default/did_indy/registry.py @@ -1,7 +1,7 @@ """DID Indy Registry""" import logging import re -from typing import Pattern +from typing import Optional, Pattern from ....config.injection_context import InjectionContext from ....core.profile import Profile @@ -12,7 +12,7 @@ GetRevStatusListResult, AnonCredsRegistryGetRevocationRegistryDefinition, ) -from ...models.anoncreds_schema import GetSchemaResult +from ...models.anoncreds_schema import AnonCredsSchema, GetSchemaResult from ...base import BaseAnonCredsRegistrar, BaseAnonCredsResolver LOGGER = logging.getLogger(__name__) @@ -33,28 +33,29 @@ async def setup(self, context: InjectionContext): """Setup.""" print("Successfully registered DIDIndyRegistry") - async def get_schema(self, profile: Profile, schema_id) -> GetSchemaResult: + async def get_schema(self, profile: Profile, schema_id: str) -> GetSchemaResult: """Get a schema from the registry.""" - - async def get_schemas(self, profile: Profile, filter: str): - """Get schema ids filtered by filter""" + raise NotImplementedError() # TODO: determine keyword arguments async def register_schema( self, profile: Profile, - options: dict, - schema, - ): + schema: AnonCredsSchema, + options: Optional[dict], + )-> SchemaResult: """Register a schema on the registry.""" + raise NotImplementedError() async def get_credential_definition( self, profile: Profile, cred_def_id: str ) -> GetCredDefResult: """Get a credential definition from the registry.""" + raise NotImplementedError() async def get_credential_definitions(self, profile: Profile, filter: str): """Get credential definition ids filtered by filter""" + raise NotImplementedError() # TODO: determine keyword arguments async def register_credential_definition( @@ -67,11 +68,13 @@ async def register_credential_definition( issuer_id: str, ): """Register a credential definition on the registry.""" + raise NotImplementedError() async def get_revocation_registry_definition( self, profile: Profile, rev_reg_id: str ) -> AnonCredsRegistryGetRevocationRegistryDefinition: """Get a revocation registry definition from the registry.""" + raise NotImplementedError() # TODO: determine keyword arguments async def register_revocation_registry_definition( @@ -81,15 +84,19 @@ async def register_revocation_registry_definition( issuer_id: str, ): """Register a revocation registry definition on the registry.""" + raise NotImplementedError() async def get_revocation_registry_definitions(self, profile: Profile, filter: str): """Get credential definition ids filtered by filter""" + raise NotImplementedError() async def get_revocation_status_list( self, profile: Profile, revocation_registry_id: str, timestamp: str ) -> GetRevStatusListResult: """Get a revocation list from the registry.""" + raise NotImplementedError() # TODO: determine keyword arguments async def register_revocation_status_list(self): """Register a revocation list on the registry.""" + raise NotImplementedError() diff --git a/aries_cloudagent/anoncreds/default/did_web/registry.py b/aries_cloudagent/anoncreds/default/did_web/registry.py index af2089c428..68756b968d 100644 --- a/aries_cloudagent/anoncreds/default/did_web/registry.py +++ b/aries_cloudagent/anoncreds/default/did_web/registry.py @@ -32,9 +32,7 @@ async def setup(self, context: InjectionContext): async def get_schema(self, profile, schema_id: str) -> GetSchemaResult: """Get a schema from the registry.""" - - async def get_schemas(self, profile: Profile, filter: str): - """Get schema ids filtered by filter""" + raise NotImplementedError() # TODO: determine keyword arguments async def register_schema( @@ -44,14 +42,17 @@ async def register_schema( schema, ): """Register a schema on the registry.""" + raise NotImplementedError() async def get_credential_definition( self, profile, credential_definition_id: str ) -> GetCredDefResult: """Get a credential definition from the registry.""" + raise NotImplementedError() async def get_credential_definitions(self, profile: Profile, filter: str): """Get credential definition ids filtered by filter""" + raise NotImplementedError() # TODO: determine keyword arguments async def register_credential_definition( @@ -64,24 +65,30 @@ async def register_credential_definition( issuer_id: str, ): """Register a credential definition on the registry.""" + raise NotImplementedError() async def get_revocation_registry_definition( self, revocation_registry_id: str ) -> AnonCredsRegistryGetRevocationRegistryDefinition: """Get a revocation registry definition from the registry.""" + raise NotImplementedError() # TODO: determine keyword arguments async def register_revocation_registry_definition(self): """Register a revocation registry definition on the registry.""" + raise NotImplementedError() async def get_revocation_registry_definitions(self, profile: Profile, filter: str): """Get credential definition ids filtered by filter""" + raise NotImplementedError() async def get_revocation_status_list( self, profile: Profile, revocation_registry_id: str, timestamp: str ) -> GetRevStatusListResult: """Get a revocation list from the registry.""" + raise NotImplementedError() # TODO: determine keyword arguments async def register_revocation_status_list(self): """Register a revocation list on the registry.""" + raise NotImplementedError() From 224b4fbf66a10cd44e080b3ac398cae942e70981 Mon Sep 17 00:00:00 2001 From: Adam Burdett Date: Thu, 6 Apr 2023 11:11:49 -0600 Subject: [PATCH 108/150] fix(anoncreds): did indy regtistry cleanup Signed-off-by: Adam Burdett --- .../anoncreds/default/did_indy/registry.py | 49 ++++++++++--------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/aries_cloudagent/anoncreds/default/did_indy/registry.py b/aries_cloudagent/anoncreds/default/did_indy/registry.py index 67564bb035..a2c7a7b575 100644 --- a/aries_cloudagent/anoncreds/default/did_indy/registry.py +++ b/aries_cloudagent/anoncreds/default/did_indy/registry.py @@ -6,13 +6,19 @@ from ....config.injection_context import InjectionContext from ....core.profile import Profile from ...models.anoncreds_cred_def import ( + CredDef, + CredDefResult, GetCredDefResult, ) from ...models.anoncreds_revocation import ( GetRevStatusListResult, AnonCredsRegistryGetRevocationRegistryDefinition, + RevRegDef, + RevRegDefResult, + RevStatusList, + RevStatusListResult, ) -from ...models.anoncreds_schema import AnonCredsSchema, GetSchemaResult +from ...models.anoncreds_schema import AnonCredsSchema, GetSchemaResult, SchemaResult from ...base import BaseAnonCredsRegistrar, BaseAnonCredsResolver LOGGER = logging.getLogger(__name__) @@ -43,60 +49,55 @@ async def register_schema( profile: Profile, schema: AnonCredsSchema, options: Optional[dict], - )-> SchemaResult: + ) -> SchemaResult: """Register a schema on the registry.""" raise NotImplementedError() async def get_credential_definition( - self, profile: Profile, cred_def_id: str + self, profile: Profile, credential_definition_id: str ) -> GetCredDefResult: """Get a credential definition from the registry.""" raise NotImplementedError() - async def get_credential_definitions(self, profile: Profile, filter: str): - """Get credential definition ids filtered by filter""" - raise NotImplementedError() - # TODO: determine keyword arguments async def register_credential_definition( self, profile: Profile, - schema_id: str, - support_revocation: bool, - tag: str, - rev_reg_size: int, - issuer_id: str, - ): + schema: GetSchemaResult, + credential_definition: CredDef, + options: Optional[dict] = None, + ) -> CredDefResult: """Register a credential definition on the registry.""" raise NotImplementedError() async def get_revocation_registry_definition( - self, profile: Profile, rev_reg_id: str + self, profile: Profile, revocation_registry_id: str ) -> AnonCredsRegistryGetRevocationRegistryDefinition: """Get a revocation registry definition from the registry.""" raise NotImplementedError() - # TODO: determine keyword arguments async def register_revocation_registry_definition( self, profile: Profile, - rev_reg_id: str, - issuer_id: str, - ): + revocation_registry_definition: RevRegDef, + options: Optional[dict] = None, + ) -> RevRegDefResult: """Register a revocation registry definition on the registry.""" raise NotImplementedError() - async def get_revocation_registry_definitions(self, profile: Profile, filter: str): - """Get credential definition ids filtered by filter""" - raise NotImplementedError() - async def get_revocation_status_list( - self, profile: Profile, revocation_registry_id: str, timestamp: str + self, profile: Profile, revocation_registry_id: str, timestamp: int ) -> GetRevStatusListResult: """Get a revocation list from the registry.""" raise NotImplementedError() # TODO: determine keyword arguments - async def register_revocation_status_list(self): + async def register_revocation_status_list( + self, + profile: Profile, + rev_reg_def: RevRegDef, + rev_status_list: RevStatusList, + options: Optional[dict] = None, + ) -> RevStatusListResult: """Register a revocation list on the registry.""" raise NotImplementedError() From c3ff93f7de17431503da2155b22350db53d576d6 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Thu, 6 Apr 2023 11:12:38 -0600 Subject: [PATCH 109/150] fix: clean up did web registry Signed-off-by: Char Howland --- .../anoncreds/default/did_web/registry.py | 76 +++++++++++-------- 1 file changed, 43 insertions(+), 33 deletions(-) diff --git a/aries_cloudagent/anoncreds/default/did_web/registry.py b/aries_cloudagent/anoncreds/default/did_web/registry.py index 68756b968d..1e1a2c8590 100644 --- a/aries_cloudagent/anoncreds/default/did_web/registry.py +++ b/aries_cloudagent/anoncreds/default/did_web/registry.py @@ -1,18 +1,20 @@ """DID Web Registry""" import re -from typing import Pattern +from typing import Optional, Pattern from ....config.injection_context import InjectionContext from ....core.profile import Profile -from ...models.anoncreds_cred_def import ( - GetCredDefResult, -) +from ...base import BaseAnonCredsRegistrar, BaseAnonCredsResolver +from ...models.anoncreds_cred_def import CredDef, CredDefResult, GetCredDefResult from ...models.anoncreds_revocation import ( - GetRevStatusListResult, AnonCredsRegistryGetRevocationRegistryDefinition, + GetRevStatusListResult, + RevRegDef, + RevRegDefResult, + RevStatusList, + RevStatusListResult, ) -from ...models.anoncreds_schema import GetSchemaResult -from ...base import BaseAnonCredsRegistrar, BaseAnonCredsResolver +from ...models.anoncreds_schema import AnonCredsSchema, GetSchemaResult, SchemaResult class DIDWebRegistry(BaseAnonCredsResolver, BaseAnonCredsRegistrar): @@ -34,61 +36,69 @@ async def get_schema(self, profile, schema_id: str) -> GetSchemaResult: """Get a schema from the registry.""" raise NotImplementedError() - # TODO: determine keyword arguments async def register_schema( self, profile: Profile, - options: dict, - schema, - ): + schema: AnonCredsSchema, + options: Optional[dict] = None, + ) -> SchemaResult: """Register a schema on the registry.""" raise NotImplementedError() async def get_credential_definition( - self, profile, credential_definition_id: str + self, profile: Profile, credential_definition_id: str ) -> GetCredDefResult: """Get a credential definition from the registry.""" raise NotImplementedError() - async def get_credential_definitions(self, profile: Profile, filter: str): - """Get credential definition ids filtered by filter""" - raise NotImplementedError() - - # TODO: determine keyword arguments async def register_credential_definition( self, profile: Profile, - schema_id: str, - support_revocation: bool, - tag: str, - rev_reg_size: int, - issuer_id: str, - ): + schema: GetSchemaResult, + credential_definition: CredDef, + options: Optional[dict] = None, + ) -> CredDefResult: """Register a credential definition on the registry.""" raise NotImplementedError() async def get_revocation_registry_definition( - self, revocation_registry_id: str + self, profile: Profile, revocation_registry_id: str ) -> AnonCredsRegistryGetRevocationRegistryDefinition: """Get a revocation registry definition from the registry.""" raise NotImplementedError() - # TODO: determine keyword arguments - async def register_revocation_registry_definition(self): + async def register_revocation_registry_definition( + self, + profile: Profile, + revocation_registry_definition: RevRegDef, + options: Optional[dict] = None, + ) -> RevRegDefResult: """Register a revocation registry definition on the registry.""" raise NotImplementedError() - async def get_revocation_registry_definitions(self, profile: Profile, filter: str): - """Get credential definition ids filtered by filter""" - raise NotImplementedError() - async def get_revocation_status_list( - self, profile: Profile, revocation_registry_id: str, timestamp: str + self, profile: Profile, revocation_registry_id: str, timestamp: int ) -> GetRevStatusListResult: """Get a revocation list from the registry.""" raise NotImplementedError() - # TODO: determine keyword arguments - async def register_revocation_status_list(self): + async def register_revocation_status_list( + self, + profile: Profile, + rev_reg_def: RevRegDef, + rev_status_list: RevStatusList, + options: Optional[dict] = None, + ) -> RevStatusListResult: """Register a revocation list on the registry.""" raise NotImplementedError() + + async def update_revocation_status_list( + self, + profile: Profile, + rev_reg_def: RevRegDef, + prev_status_list: RevStatusList, + curr_status_list: RevStatusList, + options: Optional[dict] = None, + ) -> RevStatusListResult: + """Update a revocation list on the registry.""" + raise NotImplementedError() From 528825365ed7af5b4f18117aa3e68443ad8fd681 Mon Sep 17 00:00:00 2001 From: Adam Burdett Date: Thu, 6 Apr 2023 11:17:54 -0600 Subject: [PATCH 110/150] fix(anoncreds): did indy update_revocation_status_list Signed-off-by: Adam Burdett --- .../anoncreds/default/did_indy/registry.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/aries_cloudagent/anoncreds/default/did_indy/registry.py b/aries_cloudagent/anoncreds/default/did_indy/registry.py index a2c7a7b575..e5cd7556c5 100644 --- a/aries_cloudagent/anoncreds/default/did_indy/registry.py +++ b/aries_cloudagent/anoncreds/default/did_indy/registry.py @@ -43,7 +43,6 @@ async def get_schema(self, profile: Profile, schema_id: str) -> GetSchemaResult: """Get a schema from the registry.""" raise NotImplementedError() - # TODO: determine keyword arguments async def register_schema( self, profile: Profile, @@ -59,7 +58,6 @@ async def get_credential_definition( """Get a credential definition from the registry.""" raise NotImplementedError() - # TODO: determine keyword arguments async def register_credential_definition( self, profile: Profile, @@ -91,7 +89,6 @@ async def get_revocation_status_list( """Get a revocation list from the registry.""" raise NotImplementedError() - # TODO: determine keyword arguments async def register_revocation_status_list( self, profile: Profile, @@ -101,3 +98,14 @@ async def register_revocation_status_list( ) -> RevStatusListResult: """Register a revocation list on the registry.""" raise NotImplementedError() + + async def update_revocation_status_list( + self, + profile: Profile, + rev_reg_def: RevRegDef, + prev_status_list: RevStatusList, + curr_status_list: RevStatusList, + options: Optional[dict] = None, + ) -> RevStatusListResult: + """Update a revocation list on the registry.""" + raise NotImplementedError() From 8e3e58536c6473709c57cd5e46453bc6dfd46969 Mon Sep 17 00:00:00 2001 From: Adam Burdett Date: Thu, 6 Apr 2023 11:50:55 -0600 Subject: [PATCH 111/150] fix(anoncreds): did:web regex Signed-off-by: Adam Burdett --- .../anoncreds/default/did_web/registry.py | 2 +- .../default/did_web/tests/__init__.py | 0 .../default/did_web/tests/test_registry.py | 37 +++++++++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 aries_cloudagent/anoncreds/default/did_web/tests/__init__.py create mode 100644 aries_cloudagent/anoncreds/default/did_web/tests/test_registry.py diff --git a/aries_cloudagent/anoncreds/default/did_web/registry.py b/aries_cloudagent/anoncreds/default/did_web/registry.py index 76581c3167..5b92db401d 100644 --- a/aries_cloudagent/anoncreds/default/did_web/registry.py +++ b/aries_cloudagent/anoncreds/default/did_web/registry.py @@ -20,7 +20,7 @@ class DIDWebRegistry(BaseAnonCredsResolver, BaseAnonCredsRegistrar): def __init__(self): self._supported_identifiers_regex = re.compile( - r"^(did:web:)([a-zA-Z0-9%._-]*:)*[a-zA-Z0-9%._-]+$" + r"^did:web:[a-z0-9]+(?:\.[a-z0-9]+)*(?::\d+)?(?:\/[^#\s]*)?(?:#.*)?\s*$" ) @property diff --git a/aries_cloudagent/anoncreds/default/did_web/tests/__init__.py b/aries_cloudagent/anoncreds/default/did_web/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aries_cloudagent/anoncreds/default/did_web/tests/test_registry.py b/aries_cloudagent/anoncreds/default/did_web/tests/test_registry.py new file mode 100644 index 0000000000..5a06d8d879 --- /dev/null +++ b/aries_cloudagent/anoncreds/default/did_web/tests/test_registry.py @@ -0,0 +1,37 @@ +"""Test DIDWebRegistry.""" + +import pytest +import re +from ..registry import DIDWebRegistry + +DID_WEB = re.compile( + r"^did:web:[a-z0-9]+(?:\.[a-z0-9]+)*(?::\d+)?(?:\/[^#\s]*)?(?:#.*)?\s*$" +) + + +TEST_WED_DID_0 = "did:web:example.com/anoncreds/v0/SCHEMA/asdf" +TEST_WED_DID_1 = "did:web:example.com" +TEST_WED_DID_2 = "did:web:sub.example.com" +TEST_WED_DID_3 = "did:web:example.com:8080" +TEST_WED_DID_4 = "did:web:sub.example.com/path/to/resource" +TEST_WED_DID_5 = "did:web:example.com/path/to/resource#fragment" + + +@pytest.fixture +def registry(): + """Registry fixture""" + yield DIDWebRegistry() + + +class TestLegacyIndyRegistry: + @pytest.mark.asyncio + async def test_supported_did_regex(self, registry: DIDWebRegistry): + """Test the supported_did_regex.""" + + assert registry.supported_identifiers_regex == DID_WEB + assert bool(registry.supported_identifiers_regex.match(TEST_WED_DID_0)) + assert bool(registry.supported_identifiers_regex.match(TEST_WED_DID_1)) + assert bool(registry.supported_identifiers_regex.match(TEST_WED_DID_2)) + assert bool(registry.supported_identifiers_regex.match(TEST_WED_DID_3)) + assert bool(registry.supported_identifiers_regex.match(TEST_WED_DID_4)) + assert bool(registry.supported_identifiers_regex.match(TEST_WED_DID_5)) From f384f9fa106724814141bbbc2d902788b54bb60c Mon Sep 17 00:00:00 2001 From: Char Howland Date: Thu, 6 Apr 2023 14:54:20 -0600 Subject: [PATCH 112/150] refactor: rename revocation status list ot revocation list Signed-off-by: Char Howland --- aries_cloudagent/anoncreds/base.py | 24 ++--- .../anoncreds/default/did_indy/registry.py | 24 ++--- .../anoncreds/default/did_web/registry.py | 24 ++--- .../anoncreds/default/legacy_indy/registry.py | 93 ++++++++-------- aries_cloudagent/anoncreds/holder.py | 4 +- aries_cloudagent/anoncreds/issuer.py | 60 +++++------ .../anoncreds/models/anoncreds_revocation.py | 102 +++++++++--------- aries_cloudagent/anoncreds/registry.py | 38 +++---- aries_cloudagent/anoncreds/verifier.py | 18 ++-- .../present_proof/indy/pres_exch_handler.py | 34 +++--- .../protocols/present_proof/v1_0/manager.py | 4 +- .../v2_0/formats/indy/handler.py | 4 +- .../models/issuer_rev_reg_record.py | 36 +++---- aries_cloudagent/revocation/routes.py | 14 +-- 14 files changed, 231 insertions(+), 248 deletions(-) diff --git a/aries_cloudagent/anoncreds/base.py b/aries_cloudagent/anoncreds/base.py index f619cea729..d3d3395807 100644 --- a/aries_cloudagent/anoncreds/base.py +++ b/aries_cloudagent/anoncreds/base.py @@ -11,12 +11,12 @@ GetCredDefResult, ) from .models.anoncreds_revocation import ( - GetRevStatusListResult, + GetRevListResult, AnonCredsRegistryGetRevocationRegistryDefinition, RevRegDef, RevRegDefResult, - RevStatusList, - RevStatusListResult, + RevList, + RevListResult, ) from .models.anoncreds_schema import AnonCredsSchema, GetSchemaResult, SchemaResult @@ -110,9 +110,9 @@ async def get_revocation_registry_definition( """Get a revocation registry definition from the registry.""" @abstractmethod - async def get_revocation_status_list( + async def get_revocation_list( self, profile: Profile, revocation_registry_id: str, timestamp: int - ) -> GetRevStatusListResult: + ) -> GetRevListResult: """Get a revocation list from the registry.""" @@ -146,22 +146,22 @@ async def register_revocation_registry_definition( """Register a revocation registry definition on the registry.""" @abstractmethod - async def register_revocation_status_list( + async def register_revocation_list( self, profile: Profile, rev_reg_def: RevRegDef, - rev_status_list: RevStatusList, + rev_list: RevList, options: Optional[dict] = None, - ) -> RevStatusListResult: + ) -> RevListResult: """Register a revocation list on the registry.""" @abstractmethod - async def update_revocation_status_list( + async def update_revocation_list( self, profile: Profile, rev_reg_def: RevRegDef, - prev_status_list: RevStatusList, - curr_status_list: RevStatusList, + prev_list: RevList, + curr_list: RevList, options: Optional[dict] = None, - ) -> RevStatusListResult: + ) -> RevListResult: """Update a revocation list on the registry.""" diff --git a/aries_cloudagent/anoncreds/default/did_indy/registry.py b/aries_cloudagent/anoncreds/default/did_indy/registry.py index e5cd7556c5..a262e00c74 100644 --- a/aries_cloudagent/anoncreds/default/did_indy/registry.py +++ b/aries_cloudagent/anoncreds/default/did_indy/registry.py @@ -11,12 +11,12 @@ GetCredDefResult, ) from ...models.anoncreds_revocation import ( - GetRevStatusListResult, + GetRevListResult, AnonCredsRegistryGetRevocationRegistryDefinition, RevRegDef, RevRegDefResult, - RevStatusList, - RevStatusListResult, + RevList, + RevListResult, ) from ...models.anoncreds_schema import AnonCredsSchema, GetSchemaResult, SchemaResult from ...base import BaseAnonCredsRegistrar, BaseAnonCredsResolver @@ -83,29 +83,29 @@ async def register_revocation_registry_definition( """Register a revocation registry definition on the registry.""" raise NotImplementedError() - async def get_revocation_status_list( + async def get_revocation_list( self, profile: Profile, revocation_registry_id: str, timestamp: int - ) -> GetRevStatusListResult: + ) -> GetRevListResult: """Get a revocation list from the registry.""" raise NotImplementedError() - async def register_revocation_status_list( + async def register_revocation_list( self, profile: Profile, rev_reg_def: RevRegDef, - rev_status_list: RevStatusList, + rev_list: RevList, options: Optional[dict] = None, - ) -> RevStatusListResult: + ) -> RevListResult: """Register a revocation list on the registry.""" raise NotImplementedError() - async def update_revocation_status_list( + async def update_revocation_list( self, profile: Profile, rev_reg_def: RevRegDef, - prev_status_list: RevStatusList, - curr_status_list: RevStatusList, + prev_list: RevList, + curr_list: RevList, options: Optional[dict] = None, - ) -> RevStatusListResult: + ) -> RevListResult: """Update a revocation list on the registry.""" raise NotImplementedError() diff --git a/aries_cloudagent/anoncreds/default/did_web/registry.py b/aries_cloudagent/anoncreds/default/did_web/registry.py index 67ac727ccb..db7b4a4eab 100644 --- a/aries_cloudagent/anoncreds/default/did_web/registry.py +++ b/aries_cloudagent/anoncreds/default/did_web/registry.py @@ -8,11 +8,11 @@ from ...models.anoncreds_cred_def import CredDef, CredDefResult, GetCredDefResult from ...models.anoncreds_revocation import ( AnonCredsRegistryGetRevocationRegistryDefinition, - GetRevStatusListResult, + GetRevListResult, RevRegDef, RevRegDefResult, - RevStatusList, - RevStatusListResult, + RevList, + RevListResult, ) from ...models.anoncreds_schema import AnonCredsSchema, GetSchemaResult, SchemaResult @@ -78,29 +78,29 @@ async def register_revocation_registry_definition( """Register a revocation registry definition on the registry.""" raise NotImplementedError() - async def get_revocation_status_list( + async def get_revocation_list( self, profile: Profile, revocation_registry_id: str, timestamp: int - ) -> GetRevStatusListResult: + ) -> GetRevListResult: """Get a revocation list from the registry.""" raise NotImplementedError() - async def register_revocation_status_list( + async def register_revocation_list( self, profile: Profile, rev_reg_def: RevRegDef, - rev_status_list: RevStatusList, + rev_list: RevList, options: Optional[dict] = None, - ) -> RevStatusListResult: + ) -> RevListResult: """Register a revocation list on the registry.""" raise NotImplementedError() - async def update_revocation_status_list( + async def update_revocation_list( self, profile: Profile, rev_reg_def: RevRegDef, - prev_status_list: RevStatusList, - curr_status_list: RevStatusList, + prev_list: RevList, + curr_list: RevList, options: Optional[dict] = None, - ) -> RevStatusListResult: + ) -> RevListResult: """Update a revocation list on the registry.""" raise NotImplementedError() diff --git a/aries_cloudagent/anoncreds/default/legacy_indy/registry.py b/aries_cloudagent/anoncreds/default/legacy_indy/registry.py index 1784b95877..1c2a04a787 100644 --- a/aries_cloudagent/anoncreds/default/legacy_indy/registry.py +++ b/aries_cloudagent/anoncreds/default/legacy_indy/registry.py @@ -44,13 +44,13 @@ ) from ...models.anoncreds_revocation import ( AnonCredsRegistryGetRevocationRegistryDefinition, - GetRevStatusListResult, + GetRevListResult, RevRegDef, RevRegDefResult, RevRegDefState, - RevStatusList, - RevStatusListResult, - RevStatusListState, + RevList, + RevListResult, + RevListState, ) from ...models.anoncreds_schema import ( AnonCredsSchema, @@ -436,15 +436,15 @@ async def register_revocation_registry_definition( revocation_registry_definition_metadata={"seqNo": seq_no}, ) - async def get_revocation_status_list( + async def get_revocation_list( self, profile: Profile, revocation_registry_id: str, timestamp: str - ) -> GetRevStatusListResult: + ) -> GetRevListResult: """Get a revocation list from the registry.""" async def _revoc_reg_entry_with_fix( self, profile: Profile, - rev_status_list: RevStatusList, + rev_list: RevList, rev_reg_def_type: str, entry: dict, ) -> dict: @@ -454,10 +454,10 @@ async def _revoc_reg_entry_with_fix( try: rev_entry_res = await ledger.send_revoc_reg_entry( - rev_status_list.rev_reg_id, + rev_list.rev_reg_id, rev_reg_def_type, entry, - rev_status_list.issuer_id, + rev_list.issuer_id, write_ledger=True, endorser_did=None, ) @@ -472,7 +472,7 @@ async def _revoc_reg_entry_with_fix( LOGGER.warn(err) (_, _, res) = await self.fix_ledger_entry( profile, - rev_status_list, + rev_list, True, ledger.pool.genesis_txns, ) @@ -496,69 +496,69 @@ async def _revoc_reg_entry_with_fix( return rev_entry_res - async def register_revocation_status_list( + async def register_revocation_list( self, profile: Profile, rev_reg_def: RevRegDef, - rev_status_list: RevStatusList, + rev_list: RevList, options: Optional[dict] = None, - ) -> RevStatusListResult: + ) -> RevListResult: """Register a revocation list on the registry.""" - rev_reg_entry = {"value": {"accum": rev_status_list.current_accumulator}} + rev_reg_entry = {"value": {"accum": rev_list.current_accumulator}} rev_entry_res = await self._revoc_reg_entry_with_fix( - profile, rev_status_list, rev_reg_def.type, rev_reg_entry + profile, rev_list, rev_reg_def.type, rev_reg_entry ) - return RevStatusListResult( + return RevListResult( job_id=None, - revocation_status_list_state=RevStatusListState( - state=RevStatusListState.STATE_FINISHED, - revocation_status_list=rev_status_list, + revocation_list_state=RevListState( + state=RevListState.STATE_FINISHED, + revocation_list=rev_list, ), registration_metadata={}, - revocation_status_list_metadata={ + revocation_list_metadata={ "seqNo": rev_entry_res["result"]["txnMetadata"]["seqNo"], }, ) - async def update_revocation_status_list( + async def update_revocation_list( self, profile: Profile, rev_reg_def: RevRegDef, - prev_status_list: RevStatusList, - curr_status_list: RevStatusList, + prev_list: RevList, + curr_list: RevList, options: Optional[dict] = None, - ) -> RevStatusListResult: - """Update a revocation status list.""" + ) -> RevListResult: + """Update a revocation list.""" newly_revoked_indices = [ # Remember: Indices in Indy are 1-based index + 1 for index, (prev, curr) in enumerate( - zip(prev_status_list.revocation_list, curr_status_list.revocation_list) + zip(prev_list.revocation_list, curr_list.revocation_list) ) if prev != curr ] rev_reg_entry = { "value": { - "accum": curr_status_list.current_accumulator, - "prevAccum": prev_status_list.current_accumulator, + "accum": curr_list.current_accumulator, + "prevAccum": prev_list.current_accumulator, "revoked": newly_revoked_indices, } } rev_entry_res = await self._revoc_reg_entry_with_fix( - profile, curr_status_list, rev_reg_def.type, rev_reg_entry + profile, curr_list, rev_reg_def.type, rev_reg_entry ) - return RevStatusListResult( + return RevListResult( job_id=None, - revocation_status_list_state=RevStatusListState( - state=RevStatusListState.STATE_FINISHED, - revocation_status_list=curr_status_list, + revocation_list_state=RevListState( + state=RevListState.STATE_FINISHED, + revocation_list=curr_list, ), registration_metadata={}, - revocation_status_list_metadata={ + revocation_list_metadata={ "seqNo": rev_entry_res["result"]["txnMetadata"]["seqNo"], }, ) @@ -566,7 +566,7 @@ async def update_revocation_status_list( async def fix_ledger_entry( self, profile: Profile, - rev_status_list: RevStatusList, + rev_list: RevList, apply_ledger_update: bool, genesis_transactions: str, ) -> Tuple[dict, dict, dict]: @@ -574,11 +574,9 @@ async def fix_ledger_entry( # get rev reg delta (revocations published to ledger) ledger = profile.inject(BaseLedger) async with ledger: - (rev_reg_delta, _) = await ledger.get_revoc_reg_delta( - rev_status_list.rev_reg_id - ) + (rev_reg_delta, _) = await ledger.get_revoc_reg_delta(rev_list.rev_reg_id) - # get rev reg records from wallet (revocations and status) + # get rev reg records from wallet (revocations and list) recs = [] rec_count = 0 accum_count = 0 @@ -586,7 +584,7 @@ async def fix_ledger_entry( applied_txn = {} async with profile.session() as session: recs = await IssuerCredRevRecord.query_by_ids( - session, rev_reg_id=rev_status_list.rev_reg_id + session, rev_reg_id=rev_list.rev_reg_id ) revoked_ids = [] @@ -599,8 +597,8 @@ async def fix_ledger_entry( LOGGER.debug(">>> fixed entry recs count = %s", rec_count) LOGGER.debug( - ">>> rev_status_list.revocation_list: %s", - rev_status_list.revocation_list, + ">>> rev_list.revocation_list: %s", + rev_list.revocation_list, ) LOGGER.debug( '>>> rev_reg_delta.get("value"): %s', rev_reg_delta.get("value") @@ -608,11 +606,8 @@ async def fix_ledger_entry( # if we had any revocation discrepencies, check the accumulator value if rec_count > 0: - if ( - rev_status_list.current_accumulator and rev_reg_delta.get("value") - ) and ( - rev_status_list.current_accumulator - != rev_reg_delta["value"]["accum"] + if (rev_list.current_accumulator and rev_reg_delta.get("value")) and ( + rev_list.current_accumulator != rev_reg_delta["value"]["accum"] ): # self.revoc_reg_entry = rev_reg_delta["value"] # await self.save(session) @@ -620,7 +615,7 @@ async def fix_ledger_entry( calculated_txn = await generate_ledger_rrrecovery_txn( genesis_transactions, - rev_status_list.rev_reg_id, + rev_list.rev_reg_id, revoked_ids, ) recovery_txn = json.loads(calculated_txn.to_json()) @@ -636,7 +631,7 @@ async def fix_ledger_entry( async with ledger: ledger_response = await ledger.send_revoc_reg_entry( - rev_status_list.rev_reg_id, "CL_ACCUM", recovery_txn + rev_list.rev_reg_id, "CL_ACCUM", recovery_txn ) applied_txn = ledger_response["result"] diff --git a/aries_cloudagent/anoncreds/holder.py b/aries_cloudagent/anoncreds/holder.py index c619849dc0..9b9e59dfd2 100644 --- a/aries_cloudagent/anoncreds/holder.py +++ b/aries_cloudagent/anoncreds/holder.py @@ -559,7 +559,7 @@ async def create_revocation_state( self, cred_rev_id: str, rev_reg_def: dict, - rev_status_list: dict, + rev_list: dict, tails_file_path: str, ) -> str: """ @@ -581,7 +581,7 @@ async def create_revocation_state( None, CredentialRevocationState.create, rev_reg_def, - rev_status_list, + rev_list, int(cred_rev_id), tails_file_path, ) diff --git a/aries_cloudagent/anoncreds/issuer.py b/aries_cloudagent/anoncreds/issuer.py index f0fb4a2eb7..3850865346 100644 --- a/aries_cloudagent/anoncreds/issuer.py +++ b/aries_cloudagent/anoncreds/issuer.py @@ -25,7 +25,7 @@ RevRegDef, RevRegDefResult, RevRegDefState, - RevStatusList, + RevList, ) from .models.anoncreds_schema import AnonCredsSchema, SchemaResult, SchemaState from .base import AnonCredsSchemaAlreadyExists @@ -39,7 +39,7 @@ CATEGORY_CRED_DEF_PRIVATE = "credential_def_private" CATEGORY_CRED_DEF_KEY_PROOF = "credential_def_key_proof" CATEGORY_SCHEMA = "schema" -CATEGORY_REV_STATUS_LIST = "revocation_status_list" +CATEGORY_REV_LIST = "revocation_list" CATEGORY_REV_REG_INFO = "revocation_reg_info" CATEGORY_REV_REG_DEF = "revocation_reg_def" CATEGORY_REV_REG_DEF_PRIVATE = "revocation_reg_def_private" @@ -569,10 +569,10 @@ async def get_created_revocation_registry_definitions( # entry.name was stored as the credential_definition's ID return [entry.name for entry in rev_reg_defs] - async def create_and_register_revocation_status_list( + async def create_and_register_revocation_list( self, rev_reg_def_id: str, options: dict ): - """Create and register a revocation status list.""" + """Create and register a revocation list.""" try: async with self._profile.session() as session: rev_reg_def_entry = await session.handle.fetch( @@ -590,22 +590,22 @@ async def create_and_register_revocation_status_list( issuer_id = rev_reg_def_entry.value_json["issuerId"] - rev_status_list = RevocationStatusList.create( + rev_list = RevocationStatusList.create( rev_reg_def_id, rev_reg_def_entry.raw_value, issuer_id, ) anoncreds_registry = self.profile.inject(AnonCredsRegistry) - result = await anoncreds_registry.register_revocation_status_list( - self.profile, RevStatusList.from_native(rev_status_list), options + result = await anoncreds_registry.register_revocation_list( + self.profile, RevList.from_native(rev_list), options ) - rev_status_list_json = rev_status_list.to_json() + rev_list_json = rev_list.to_json() try: async with self._profile.session() as session: await session.handle.insert( - CATEGORY_REV_STATUS_LIST, rev_reg_def_id, rev_status_list_json + CATEGORY_REV_LIST, rev_reg_def_id, rev_list_json ) except AskarError as err: raise AnonCredsIssuerError("Error saving new revocation registry") from err @@ -717,9 +717,7 @@ async def create_credential( if revoc_reg_id: try: async with self._profile.transaction() as txn: - rev_status_list = await txn.handle.fetch( - CATEGORY_REV_STATUS_LIST, revoc_reg_id - ) + rev_list = await txn.handle.fetch(CATEGORY_REV_LIST, revoc_reg_id) rev_reg_info = await txn.handle.fetch( CATEGORY_REV_REG_INFO, revoc_reg_id, for_update=True ) @@ -729,7 +727,7 @@ async def create_credential( rev_key = await txn.handle.fetch( CATEGORY_REV_REG_DEF_PRIVATE, revoc_reg_id ) - if not rev_status_list: + if not rev_list: raise AnonCredsIssuerError("Revocation registry not found") if not rev_reg_info: raise AnonCredsIssuerError( @@ -754,9 +752,7 @@ async def create_credential( rev_reg_def = RevocationRegistryDefinition.load( rev_reg_def.raw_value ) - rev_status_list = RevocationStatusList.load( - rev_status_list.raw_value - ) + rev_list = RevocationStatusList.load(rev_list.raw_value) except AnoncredsError as err: raise AnonCredsIssuerError( "Error loading revocation registry definition" @@ -785,7 +781,7 @@ async def create_credential( else: revoc = None credential_revocation_id = None - rev_status_list = None + rev_list = None try: credential = await asyncio.get_event_loop().run_in_executor( @@ -798,7 +794,7 @@ async def create_credential( raw_values, None, revoc_reg_id, - rev_status_list, + rev_list, revoc, ), ) @@ -822,13 +818,13 @@ async def revoke_credentials( cred_revoc_ids: sequences of credential indexes in the revocation registry Returns: - Tuple with the update revocation status list, list of cred rev ids not revoked + Tuple with the update revocation list, list of cred rev ids not revoked """ - # TODO This method should return the old status list, the new status list, + # TODO This method should return the old list, the new list, # and the list of changed indices - prev_status_list = None + prev_list = None updated_list = None failed_crids = set() max_attempt = 5 @@ -845,8 +841,8 @@ async def revoke_credentials( rev_reg_def_entry = await session.handle.fetch( CATEGORY_REV_REG_DEF, revoc_reg_id ) - rev_status_list_entry = await session.handle.fetch( - CATEGORY_REV_STATUS_LIST, revoc_reg_id + rev_list_entry = await session.handle.fetch( + CATEGORY_REV_LIST, revoc_reg_id ) rev_reg_info = await session.handle.fetch( CATEGORY_REV_REG_INFO, revoc_reg_id @@ -855,7 +851,7 @@ async def revoke_credentials( raise AnonCredsIssuerError( "Revocation registry definition not found" ) - if not rev_status_list_entry: + if not rev_list_entry: raise AnonCredsIssuerError("Revocation registry not found") if not rev_reg_info: raise AnonCredsIssuerError("Revocation registry metadata not found") @@ -912,16 +908,14 @@ async def revoke_credentials( break try: - prev_status_list = RevocationStatusList.load( - rev_status_list_entry.raw_value - ) + prev_list = RevocationStatusList.load(rev_list_entry.raw_value) except AnoncredsError as err: raise AnonCredsIssuerError("Error loading revocation registry") from err try: updated_list = await asyncio.get_event_loop().run_in_executor( None, - lambda: prev_status_list.update( + lambda: prev_list.update( int(time.time()), None, # issued list(rev_crids), # revoked @@ -935,13 +929,13 @@ async def revoke_credentials( try: async with self._profile.transaction() as txn: - rev_status_list_upd = await txn.handle.fetch( - CATEGORY_REV_STATUS_LIST, revoc_reg_id, for_update=True + rev_list_upd = await txn.handle.fetch( + CATEGORY_REV_LIST, revoc_reg_id, for_update=True ) rev_info_upd = await txn.handle.fetch( CATEGORY_REV_REG_INFO, revoc_reg_id, for_update=True ) - if not rev_status_list_upd or not rev_reg_info: + if not rev_list_upd or not rev_reg_info: LOGGER.warn( "Revocation registry missing, skipping update: {}", revoc_reg_id, @@ -953,7 +947,7 @@ async def revoke_credentials( # handle concurrent update to the registry by retrying continue await txn.handle.replace( - CATEGORY_REV_STATUS_LIST, + CATEGORY_REV_LIST, revoc_reg_id, updated_list.to_json_buffer(), ) @@ -968,7 +962,7 @@ async def revoke_credentials( break return RevokeResult( - prev=prev_status_list, + prev=prev_list, curr=updated_list, failed=[str(rev_id) for rev_id in sorted(failed_crids)], ) diff --git a/aries_cloudagent/anoncreds/models/anoncreds_revocation.py b/aries_cloudagent/anoncreds/models/anoncreds_revocation.py index 8fad153730..1d25b3e26a 100644 --- a/aries_cloudagent/anoncreds/models/anoncreds_revocation.py +++ b/aries_cloudagent/anoncreds/models/anoncreds_revocation.py @@ -271,11 +271,11 @@ class Meta: ) -class RevStatusList(BaseModel): - """RevStatusList.""" +class RevList(BaseModel): + """RevList.""" class Meta: - """RevStatusList metadata.""" + """RevList metadata.""" schema_class = "AnonCredsRevocationListSchema" @@ -296,22 +296,22 @@ def __init__( self.timestamp = timestamp @classmethod - def from_native(cls, rev_status_list: RevocationStatusList): - """Convert from native revocation status list.""" - return cls.deserialize(rev_status_list.to_json()) + def from_native(cls, rev_list: RevocationStatusList): + """Convert from native revocation list.""" + return cls.deserialize(rev_list.to_json()) def to_native(self): - """Convert to native revocation status list.""" + """Convert to native revocation list.""" return RevocationStatusList.load(self.serialize()) -class RevStatusListSchema(BaseModelSchema): - """RevStatusListSchema.""" +class RevListSchema(BaseModelSchema): + """RevListSchema.""" class Meta: - """RevStatusListSchema metadata.""" + """RevListSchema metadata.""" - model_class = RevStatusList + model_class = RevList unknown = EXCLUDE issuer_id = fields.Str( @@ -329,8 +329,8 @@ class Meta: timestamp = fields.Int() -class RevStatusListState(BaseModel): - """RevStatusListState.""" +class RevListState(BaseModel): + """RevListState.""" STATE_FINISHED = "finished" STATE_FAILED = "failed" @@ -338,97 +338,95 @@ class RevStatusListState(BaseModel): STATE_WAIT = "wait" class Meta: - """RevStatusListState metadata.""" + """RevListState metadata.""" - schema_class = "RevStatusListStateSchema" + schema_class = "RevListStateSchema" def __init__( self, state: str, - revocation_status_list: RevStatusList, + revocation_list: RevList, ): self.state = state - self.revocation_status_list = revocation_status_list + self.revocation_list = revocation_list -class RevStatusListStateSchema(BaseModelSchema): - """RevStatusListStateSchema.""" +class RevListStateSchema(BaseModelSchema): + """RevListStateSchema.""" class Meta: - """RevStatusListStateSchema metadata.""" + """RevListStateSchema metadata.""" - model_class = RevStatusListState + model_class = RevListState unknown = EXCLUDE state = fields.Str( validate=OneOf( [ - RevStatusListState.STATE_FINISHED, - RevStatusListState.STATE_FAILED, - RevStatusListState.STATE_ACTION, - RevStatusListState.STATE_WAIT, + RevListState.STATE_FINISHED, + RevListState.STATE_FAILED, + RevListState.STATE_ACTION, + RevListState.STATE_WAIT, ] ) ) - revocation_status_list = fields.Nested( - RevStatusListSchema(), description="revocation list" - ) + revocation_list = fields.Nested(RevListSchema(), description="revocation list") -class RevStatusListResult(BaseModel): +class RevListResult(BaseModel): """Cred def result.""" class Meta: - """RevStatusListResult metadata.""" + """RevListResult metadata.""" - schema_class = "RevStatusListResultSchema" + schema_class = "RevListResultSchema" def __init__( self, job_id: Optional[str], - revocation_status_list_state: RevStatusListState, + revocation_list_state: RevListState, registration_metadata: dict, - revocation_status_list_metadata: dict, + revocation_list_metadata: dict, **kwargs, ): super().__init__(**kwargs) self.job_id = job_id - self.revocation_status_list_state = revocation_status_list_state + self.revocations_list_state = revocation_list_state self.registration_metadata = registration_metadata - self.revocation_status_list_metadata = revocation_status_list_metadata + self.revocation_list_metadata = revocation_list_metadata @property def rev_reg_def_id(self): - return self.revocation_status_list_state.revocation_status_list_id + return self.revocation_list_state.revocation_list_id -class RevStatusListResultSchema(BaseModelSchema): +class RevListResultSchema(BaseModelSchema): """Cred def result schema.""" class Meta: - """RevStatusListResultSchema metadata.""" + """RevListResultSchema metadata.""" - model_class = RevStatusListResult + model_class = RevListResult unknown = EXCLUDE job_id = fields.Str() - revocation_status_list_state = fields.Nested(RevStatusListStateSchema()) + revocation_list_state = fields.Nested(RevListStateSchema()) registration_metadata = fields.Dict() - # For indy, revocation_status_list_metadata will contain the seqNo - revocation_status_list_metadata = fields.Dict() + # For indy, revocation_list_metadata will contain the seqNo + revocation_list_metadata = fields.Dict() -class GetRevStatusListResult(BaseModel): - """GetRevStatusListResult""" +class GetRevListResult(BaseModel): + """GetRevListResult""" class Meta: - """GetRevStatusListResult metadata.""" + """GetRevListResult metadata.""" - schema_class = "GetRevStatusListResultSchema" + schema_class = "GetRevListResultSchema" def __init__( self, - revocation_list: RevStatusList, + revocation_list: RevList, resolution_metadata: Dict[str, Any], revocation_registry_metadata: Dict[str, Any], **kwargs, @@ -439,15 +437,15 @@ def __init__( self.revocation_registry_metadata = revocation_registry_metadata -class GetRevStatusListResultSchema(BaseModelSchema): - """GetRevStatusListResultSchema""" +class GetRevListResultSchema(BaseModelSchema): + """GetRevListResultSchema""" class Meta: - """GetRevStatusListResultSchema metadata.""" + """GetRevListResultSchema metadata.""" - model_class = GetRevStatusListResult + model_class = GetRevListResult unknown = EXCLUDE - revocation_list = fields.Nested(RevStatusListSchema) + revocation_list = fields.Nested(RevListSchema) resolution_metadata = fields.Str() revocation_registry_metadata = fields.Dict() diff --git a/aries_cloudagent/anoncreds/registry.py b/aries_cloudagent/anoncreds/registry.py index 55383372a6..c3b9b9b6bb 100644 --- a/aries_cloudagent/anoncreds/registry.py +++ b/aries_cloudagent/anoncreds/registry.py @@ -10,12 +10,12 @@ GetCredDefResult, ) from .models.anoncreds_revocation import ( - GetRevStatusListResult, + GetRevListResult, AnonCredsRegistryGetRevocationRegistryDefinition, RevRegDef, RevRegDefResult, - RevStatusList, - RevStatusListResult, + RevList, + RevListResult, ) from .models.anoncreds_schema import AnonCredsSchema, GetSchemaResult, SchemaResult from .base import ( @@ -140,38 +140,38 @@ async def register_revocation_registry_definition( profile, revocation_registry_definition, options ) - async def get_revocation_status_list( + async def get_revocation_list( self, profile: Profile, revocation_registry_id: str, timestamp: int - ) -> GetRevStatusListResult: + ) -> GetRevListResult: """Get a revocation list from the registry.""" resolver = await self._resolver_for_identifier(revocation_registry_id) - return await resolver.get_revocation_status_list( + return await resolver.get_revocation_list( profile, revocation_registry_id, timestamp ) - async def register_revocation_status_list( + async def register_revocation_list( self, profile: Profile, rev_reg_def: RevRegDef, - rev_status_list: RevStatusList, + rev_list: RevList, options: Optional[dict] = None, - ) -> RevStatusListResult: + ) -> RevListResult: """Register a revocation list on the registry.""" - registrar = await self._registrar_for_identifier(rev_status_list.issuer_id) - return await registrar.register_revocation_status_list( - profile, rev_reg_def, rev_status_list, options + registrar = await self._registrar_for_identifier(rev_list.issuer_id) + return await registrar.register_revocation_list( + profile, rev_reg_def, rev_list, options ) - async def update_revocation_status_list( + async def update_revocation_list( self, profile: Profile, rev_reg_def: RevRegDef, - prev_status_list: RevStatusList, - curr_status_list: RevStatusList, + prev_list: RevList, + curr_list: RevList, options: Optional[dict] = None, - ) -> RevStatusListResult: + ) -> RevListResult: """Update a revocation list on the registry.""" - registrar = await self._registrar_for_identifier(curr_status_list.issuer_id) - return await registrar.update_revocation_status_list( - profile, rev_reg_def, prev_status_list, curr_status_list, options + registrar = await self._registrar_for_identifier(curr_list.issuer_id) + return await registrar.update_revocation_list( + profile, rev_reg_def, prev_list, curr_list, options ) diff --git a/aries_cloudagent/anoncreds/verifier.py b/aries_cloudagent/anoncreds/verifier.py index 3299f6846f..eaa29901dc 100644 --- a/aries_cloudagent/anoncreds/verifier.py +++ b/aries_cloudagent/anoncreds/verifier.py @@ -380,14 +380,14 @@ async def process_pres_identifiers( self, identifiers: list, ) -> Tuple[dict, dict, dict, dict]: - """Return schemas, cred_defs, rev_reg_defs, rev_status_lists.""" + """Return schemas, cred_defs, rev_reg_defs, rev_lists.""" schema_ids = [] cred_def_ids = [] schemas = {} cred_defs = {} rev_reg_defs = {} - rev_status_lists = {} + rev_lists = {} for identifier in identifiers: schema_ids.append(identifier["schema_id"]) @@ -417,25 +417,25 @@ async def process_pres_identifiers( ).revocation_registry.serialize() if identifier.get("timestamp"): - rev_status_lists.setdefault(identifier["rev_reg_id"], {}) + rev_lists.setdefault(identifier["rev_reg_id"], {}) if ( identifier["timestamp"] - not in rev_status_lists[identifier["rev_reg_id"]] + not in rev_lists[identifier["rev_reg_id"]] ): - result = await anoncreds_registry.get_revocation_status_list( + result = await anoncreds_registry.get_revocation_list( self.profile, identifier["rev_reg_id"], identifier["timestamp"], ) - rev_status_lists[identifier["rev_reg_id"]][ + rev_lists[identifier["rev_reg_id"]][ identifier["timestamp"] ] = result.revocation_list.serialize() return ( schemas, cred_defs, rev_reg_defs, - rev_status_lists, + rev_lists, ) async def verify_presentation( @@ -445,7 +445,7 @@ async def verify_presentation( schemas, credential_definitions, rev_reg_defs, - rev_status_lists, + rev_lists, ) -> Tuple[bool, list]: """ Verify a presentation. @@ -484,7 +484,7 @@ async def verify_presentation( schemas, credential_definitions, rev_reg_defs, - rev_status_lists, + rev_lists, ) except AnoncredsError as err: s = str(err) diff --git a/aries_cloudagent/protocols/present_proof/indy/pres_exch_handler.py b/aries_cloudagent/protocols/present_proof/indy/pres_exch_handler.py index aea39cc9e5..c6afa0b901 100644 --- a/aries_cloudagent/protocols/present_proof/indy/pres_exch_handler.py +++ b/aries_cloudagent/protocols/present_proof/indy/pres_exch_handler.py @@ -132,16 +132,14 @@ async def _get_ledger_objects(self, credentials: dict): ) return schemas, cred_defs, revocation_registries - async def _get_revocation_status_lists( - self, requested_referents: dict, credentials: dict - ): - """Get revocation status lists. + async def _get_revocation_lists(self, requested_referents: dict, credentials: dict): + """Get revocation lists. - Get revocation status lists with non-revocation interval defined in + Get revocation lists with non-revocation interval defined in "non_revoked" of the presentation request or attributes """ epoch_now = int(time.time()) - rev_status_lists = {} + rev_lists = {} for precis in requested_referents.values(): # cred_id, non-revoc interval credential_id = precis["cred_id"] if not credentials[credential_id].get("rev_reg_id"): @@ -158,14 +156,14 @@ async def _get_revocation_status_lists( f"{reft_non_revoc_interval.get('from', 0)}_" f"{reft_non_revoc_interval.get('to', epoch_now)}" ) - if key not in rev_status_lists: - result = await anoncreds_registry.get_revocation_status_list( + if key not in rev_lists: + result = await anoncreds_registry.get_revocation_list( self._profile, rev_reg_id, reft_non_revoc_interval.get("to", epoch_now), ) - rev_status_lists[key] = ( + rev_lists[key] = ( rev_reg_id, credential_id, result.revocation_list.serialize(), @@ -174,21 +172,21 @@ async def _get_revocation_status_lists( for stamp_me in requested_referents.values(): # often one cred satisfies many requested attrs/preds if stamp_me["cred_id"] == credential_id: - stamp_me["timestamp"] = rev_status_lists[key][3] + stamp_me["timestamp"] = rev_lists[key][3] - return rev_status_lists + return rev_lists async def _get_revocation_states( - self, revocation_registries: dict, credentials: dict, rev_status_lists: dict + self, revocation_registries: dict, credentials: dict, rev_lists: dict ): """Get revocation states to prove non-revoked.""" revocation_states = {} for ( rev_reg_id, credential_id, - rev_status_list, + rev_list, timestamp, - ) in rev_status_lists.values(): + ) in rev_lists.values(): if rev_reg_id not in revocation_states: revocation_states[rev_reg_id] = {} rev_reg = revocation_registries[rev_reg_id] @@ -198,7 +196,7 @@ async def _get_revocation_states( await self.holder.create_revocation_state( credentials[credential_id]["cred_rev_id"], rev_reg.reg_def, - rev_status_list, + rev_list, tails_local_path, ) ) @@ -242,12 +240,10 @@ async def return_presentation( credentials ) - rev_status_lists = await self._get_revocation_status_lists( - requested_referents, credentials - ) + rev_lists = await self._get_revocation_lists(requested_referents, credentials) revocation_states = await self._get_revocation_states( - revocation_registries, credentials, rev_status_lists + revocation_registries, credentials, rev_lists ) self._set_timestamps(requested_credentials, requested_referents) diff --git a/aries_cloudagent/protocols/present_proof/v1_0/manager.py b/aries_cloudagent/protocols/present_proof/v1_0/manager.py index 208483a2c8..e2ea35e180 100644 --- a/aries_cloudagent/protocols/present_proof/v1_0/manager.py +++ b/aries_cloudagent/protocols/present_proof/v1_0/manager.py @@ -415,7 +415,7 @@ async def verify_presentation( schemas, cred_defs, rev_reg_defs, - rev_status_lists, + rev_lists, ) = await verifier.process_pres_identifiers(indy_proof["identifiers"]) verifier = self._profile.inject(AnonCredsVerifier) @@ -427,7 +427,7 @@ async def verify_presentation( schemas, cred_defs, rev_reg_defs, - rev_status_lists, + rev_lists, ) presentation_exchange_record.verified = json.dumps(verified_bool) presentation_exchange_record.verified_msgs = list(set(verified_msgs)) diff --git a/aries_cloudagent/protocols/present_proof/v2_0/formats/indy/handler.py b/aries_cloudagent/protocols/present_proof/v2_0/formats/indy/handler.py index 42dad332b7..b5303da6fe 100644 --- a/aries_cloudagent/protocols/present_proof/v2_0/formats/indy/handler.py +++ b/aries_cloudagent/protocols/present_proof/v2_0/formats/indy/handler.py @@ -326,7 +326,7 @@ async def verify_pres(self, pres_ex_record: V20PresExRecord) -> V20PresExRecord: schemas, cred_defs, rev_reg_defs, - rev_status_lists, + rev_lists, ) = await verifier.process_pres_identifiers(indy_proof["identifiers"]) verifier = AnonCredsVerifier(self._profile) @@ -337,7 +337,7 @@ async def verify_pres(self, pres_ex_record: V20PresExRecord) -> V20PresExRecord: schemas, cred_defs, rev_reg_defs, - rev_status_lists, + rev_lists, ) pres_ex_record.verified = json.dumps(verified) pres_ex_record.verified_msgs = list(set(verified_msgs)) diff --git a/aries_cloudagent/revocation/models/issuer_rev_reg_record.py b/aries_cloudagent/revocation/models/issuer_rev_reg_record.py index 5fc1d7f5c5..4d826124b7 100644 --- a/aries_cloudagent/revocation/models/issuer_rev_reg_record.py +++ b/aries_cloudagent/revocation/models/issuer_rev_reg_record.py @@ -16,9 +16,9 @@ from ...anoncreds.models.anoncreds_revocation import ( RevRegDef, RevRegDefSchema, - RevStatusList, - RevStatusListResult, - RevStatusListSchema, + RevList, + RevListResult, + RevListSchema, ) from ...anoncreds.registry import AnonCredsRegistry from ...anoncreds.util import indy_client_dir @@ -79,7 +79,7 @@ def __init__( revoc_def_type: str = None, revoc_reg_id: str = None, revoc_reg_def: Union[RevRegDef, Mapping] = None, - rev_status_list: Union[RevStatusList, Mapping] = None, + rev_list: Union[RevList, Mapping] = None, tag: str = None, tails_hash: str = None, tails_local_path: str = None, @@ -99,7 +99,7 @@ def __init__( self.revoc_def_type = revoc_def_type or self.REVOC_DEF_TYPE_CL self.revoc_reg_id = revoc_reg_id self._revoc_reg_def = RevRegDef.serde(revoc_reg_def) - self._revoc_reg_entry = RevStatusList.serde(rev_status_list) + self._revoc_reg_entry = RevList.serde(rev_list) self.tag = tag self.tails_hash = tails_hash self.tails_local_path = tails_local_path @@ -125,14 +125,14 @@ def revoc_reg_def(self, value): self._revoc_reg_def = RevRegDef.serde(value) @property - def rev_status_list(self) -> RevStatusList: + def rev_list(self) -> RevList: """Accessor; get deserialized.""" return None if self._revoc_reg_entry is None else self._revoc_reg_entry.de - @rev_status_list.setter - def rev_status_list(self, value): + @rev_list.setter + def rev_list(self, value): """Setter; store de/serialized views.""" - self._revoc_reg_entry = RevStatusList.serde(value) + self._revoc_reg_entry = RevList.serde(value) @property def record_value(self) -> Mapping: @@ -154,7 +154,7 @@ def record_value(self) -> Mapping: prop: getattr(self, f"_{prop}").ser for prop in ( "revoc_reg_def", - "rev_status_list", + "rev_list", ) if getattr(self, prop) is not None }, @@ -318,12 +318,12 @@ async def send_entry( options: Optional[dict] = None, write_ledger: bool = True, # TODO Delete me endorser_did: str = None, # TODO Delete me - ) -> RevStatusListResult: + ) -> RevListResult: """Send a registry entry to the ledger.""" if not ( self.revoc_reg_id and self.revoc_def_type - and self.rev_status_list + and self.rev_list and self.issuer_id ): raise RevocationError("Revocation registry undefined") @@ -342,17 +342,17 @@ async def send_entry( ) anoncreds_registry = profile.inject(AnonCredsRegistry) - rev_entry_res = await anoncreds_registry.register_revocation_status_list( + rev_entry_res = await anoncreds_registry.register_revocation_list( profile, self.revoc_reg_def, - self.rev_status_list, + self.rev_list, options, ) if self.state == IssuerRevRegRecord.STATE_POSTED: self.state = ( IssuerRevRegRecord.STATE_ACTIVE - ) # registering rev status list activates + ) # registering rev list activates async with profile.session() as session: await self.save( session, reason="Published initial revocation registry entry" @@ -360,7 +360,7 @@ async def send_entry( return rev_entry_res - # TODO Update to align with rev status list (if that's even necessary; does + # TODO Update to align with rev list (if that's even necessary; does # this move to indy registry?) async def fix_ledger_entry( self, @@ -623,8 +623,8 @@ class Meta: required=False, description="Revocation registry definition", ) - rev_status_list = fields.Nested( - RevStatusListSchema(), required=False, description="Revocation registry entry" + rev_list = fields.Nested( + RevListSchema(), required=False, description="Revocation registry entry" ) tag = fields.Str( required=False, description="Tag within issuer revocation registry identifier" diff --git a/aries_cloudagent/revocation/routes.py b/aries_cloudagent/revocation/routes.py index a31e29bd36..2dc5ca428d 100644 --- a/aries_cloudagent/revocation/routes.py +++ b/aries_cloudagent/revocation/routes.py @@ -293,8 +293,8 @@ class CredRevRecordDetailsResultSchema(OpenAPISchema): class CredRevIndyRecordsResultSchema(OpenAPISchema): """Result schema for revoc reg delta.""" - rev_status_list = fields.Dict( - description="revocation status list", + rev_list = fields.Dict( + description="revocation list", ) @@ -725,7 +725,7 @@ async def get_rev_reg_issued(request: web.BaseRequest): ) @match_info_schema(RevRegIdMatchInfoSchema()) @response_schema(CredRevIndyRecordsResultSchema(), 200, description="") -async def get_rev_status_list(request: web.BaseRequest): +async def get_rev_list(request: web.BaseRequest): """ Request handler to get details of revoked credentials from ledger. @@ -741,13 +741,13 @@ async def get_rev_status_list(request: web.BaseRequest): rev_reg_id = request.match_info["rev_reg_id"] anoncreds_registry = context.inject(AnonCredsRegistry) - rev_status_list = await anoncreds_registry.get_revocation_status_list( + rev_list = await anoncreds_registry.get_revocation_list( context.profile, rev_reg_id, int(time()) ) return web.json_response( { - "rev_status_list": rev_status_list, + "rev_list": rev_list, } ) @@ -842,7 +842,7 @@ async def update_rev_reg_revoked_state(request: web.BaseRequest): @docs( tags=["revocation"], - summary="Get credential revocation status", + summary="Get credential revocation", ) @querystring_schema(CredRevRecordQueryStringSchema()) @response_schema(CredRevRecordResultSchema(), 200, description="") @@ -1571,7 +1571,7 @@ async def register(app: web.Application): ), web.get( "/revocation/registry/{rev_reg_id}/issued/indy_recs", - get_rev_status_list, + get_rev_list, allow_head=False, ), web.post("/revocation/create-registry", create_rev_reg), From 02f1b82ebe445001a78f89b1dc695c2c09ffd2c9 Mon Sep 17 00:00:00 2001 From: Adam Burdett Date: Thu, 6 Apr 2023 15:25:46 -0600 Subject: [PATCH 113/150] fix(flake8): made flake8 happier Signed-off-by: Adam Burdett --- aries_cloudagent/anoncreds/base.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/aries_cloudagent/anoncreds/base.py b/aries_cloudagent/anoncreds/base.py index f619cea729..dc82c35033 100644 --- a/aries_cloudagent/anoncreds/base.py +++ b/aries_cloudagent/anoncreds/base.py @@ -1,4 +1,4 @@ -"""Base Registry""" +"""Base Registry.""" from abc import ABC, abstractmethod from typing import Generic, Optional, Pattern, Tuple, TypeVar @@ -34,6 +34,7 @@ class AnonCredsObjectNotFound(BaseAnonCredsError): def __init__( self, message: Optional[str] = None, resolution_metadata: Optional[dict] = None ): + """Constructor.""" super().__init__(message, resolution_metadata) self.resolution_metadata = resolution_metadata @@ -48,11 +49,13 @@ class AnonCredsObjectAlreadyExists(AnonCredsRegistrationError, Generic[T]): def __init__( self, message: Optional[str] = None, obj: Optional[T] = None, *args, **kwargs ): + """Constructor.""" super().__init__(message, obj, *args, **kwargs) self.obj = obj @property def message(self): + """Message property.""" if self.args[0] and self.args[1]: return f"{self.args[0]}: {self.args[1]}" else: @@ -66,10 +69,12 @@ class AnonCredsSchemaAlreadyExists( @property def schema_id(self): + """Get Schema Id.""" return self.obj[0] if self.obj else None @property def schema(self): + """Get Schema.""" return self.obj[1] if self.obj else None @@ -78,6 +83,8 @@ class AnonCredsResolutionError(BaseAnonCredsError): class BaseAnonCredsHandler(ABC): + """Base Anon Creds Handler.""" + @property @abstractmethod def supported_identifiers_regex(self) -> Pattern: @@ -89,10 +96,12 @@ async def supports(self, identifier: str) -> bool: @abstractmethod async def setup(self, context: InjectionContext): - """Setup method.""" + """Class Setup method.""" class BaseAnonCredsResolver(BaseAnonCredsHandler): + """Base Anon Creds Resolver.""" + @abstractmethod async def get_schema(self, profile: Profile, schema_id: str) -> GetSchemaResult: """Get a schema from the registry.""" @@ -117,6 +126,8 @@ async def get_revocation_status_list( class BaseAnonCredsRegistrar(BaseAnonCredsHandler): + """Base Anon Creds Registrar.""" + @abstractmethod async def register_schema( self, From af733b8fab1dff26b21b0d452f181279321e2777 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Thu, 27 Apr 2023 10:32:06 -0400 Subject: [PATCH 114/150] test: rev reg def and rev reg list posts in test Signed-off-by: Daniel Bluhm --- anoncreds_test.py | 17 +++++++++++++++++ .../anoncreds/default/did_web/registry.py | 11 +++++++++++ 2 files changed, 28 insertions(+) diff --git a/anoncreds_test.py b/anoncreds_test.py index 8417503501..2c407a8d19 100644 --- a/anoncreds_test.py +++ b/anoncreds_test.py @@ -55,6 +55,23 @@ async def main(): cred_def = await alice.get(f"/anoncreds/credential-definition/{cred_def_id}") cred_defs = await alice.get("/anoncreds/credential-definitions") + rev_reg_def = await alice.post( + "/anoncreds/revocation-registry-definition", + json={ + "issuerId": public_did.did, + "credDefId": cred_def_id, + "tag": "default", + "maxCredNum": 10, + }, + ) + rev_status_list = await alice.post( + "/anoncreds/revocation-status-list", + json={ + "revRegDefId": rev_reg_def["revocation_registry_definition_state"][ + "revocation_registry_definition_id" + ] + }, + ) alice_conn, bob_conn = await didexchange(alice, bob) await indy_issue_credential_v2( alice, diff --git a/aries_cloudagent/anoncreds/default/did_web/registry.py b/aries_cloudagent/anoncreds/default/did_web/registry.py index db7b4a4eab..dea890858b 100644 --- a/aries_cloudagent/anoncreds/default/did_web/registry.py +++ b/aries_cloudagent/anoncreds/default/did_web/registry.py @@ -4,6 +4,17 @@ from ....config.injection_context import InjectionContext from ....core.profile import Profile +from ...models.anoncreds_cred_def import ( + GetCredDefResult, +) +from ...models.anoncreds_revocation import ( + GetRevListResult, + AnonCredsRegistryGetRevocationRegistryDefinition, + RevRegDef, + RevList, + RevListResult, +) +from ...models.anoncreds_schema import GetSchemaResult from ...base import BaseAnonCredsRegistrar, BaseAnonCredsResolver from ...models.anoncreds_cred_def import CredDef, CredDefResult, GetCredDefResult from ...models.anoncreds_revocation import ( From c422b2f22726d01794426771f789a3c393d86229 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Thu, 27 Apr 2023 10:36:05 -0400 Subject: [PATCH 115/150] fix: rev reg def models Signed-off-by: Daniel Bluhm --- .../anoncreds/default/legacy_indy/registry.py | 22 +++++++++++-------- .../anoncreds/models/anoncreds_revocation.py | 21 +++++++++++------- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/aries_cloudagent/anoncreds/default/legacy_indy/registry.py b/aries_cloudagent/anoncreds/default/legacy_indy/registry.py index 1c2a04a787..c1ae812bde 100644 --- a/aries_cloudagent/anoncreds/default/legacy_indy/registry.py +++ b/aries_cloudagent/anoncreds/default/legacy_indy/registry.py @@ -358,13 +358,13 @@ async def register_credential_definition( ) async def get_revocation_registry_definition( - self, profile: Profile, rev_reg_id: str + self, profile: Profile, rev_reg_def_id: str ) -> AnonCredsRegistryGetRevocationRegistryDefinition: """Get a revocation registry definition from the registry.""" try: revoc = AnonCredsRevocation(profile) - rev_reg = await revoc.get_issuer_rev_reg_record(rev_reg_id) + rev_reg = await revoc.get_issuer_rev_reg_record(rev_reg_def_id) except StorageNotFoundError as err: raise AnonCredsResolutionError(err) @@ -454,7 +454,7 @@ async def _revoc_reg_entry_with_fix( try: rev_entry_res = await ledger.send_revoc_reg_entry( - rev_list.rev_reg_id, + rev_list.rev_reg_def_id, rev_reg_def_type, entry, rev_list.issuer_id, @@ -504,7 +504,10 @@ async def register_revocation_list( options: Optional[dict] = None, ) -> RevListResult: """Register a revocation list on the registry.""" - rev_reg_entry = {"value": {"accum": rev_list.current_accumulator}} + rev_reg_entry = { + "ver": "1.0", + "value": {"accum": rev_list.current_accumulator} + } rev_entry_res = await self._revoc_reg_entry_with_fix( profile, rev_list, rev_reg_def.type, rev_reg_entry @@ -540,11 +543,12 @@ async def update_revocation_list( if prev != curr ] rev_reg_entry = { + "ver": "1.0", "value": { "accum": curr_list.current_accumulator, "prevAccum": prev_list.current_accumulator, "revoked": newly_revoked_indices, - } + }, } rev_entry_res = await self._revoc_reg_entry_with_fix( @@ -574,7 +578,7 @@ async def fix_ledger_entry( # get rev reg delta (revocations published to ledger) ledger = profile.inject(BaseLedger) async with ledger: - (rev_reg_delta, _) = await ledger.get_revoc_reg_delta(rev_list.rev_reg_id) + (rev_reg_delta, _) = await ledger.get_revoc_reg_delta(rev_list.rev_reg_def_id) # get rev reg records from wallet (revocations and list) recs = [] @@ -584,7 +588,7 @@ async def fix_ledger_entry( applied_txn = {} async with profile.session() as session: recs = await IssuerCredRevRecord.query_by_ids( - session, rev_reg_id=rev_list.rev_reg_id + session, rev_reg_id=rev_list.rev_reg_def_id ) revoked_ids = [] @@ -615,7 +619,7 @@ async def fix_ledger_entry( calculated_txn = await generate_ledger_rrrecovery_txn( genesis_transactions, - rev_list.rev_reg_id, + rev_list.rev_reg_def_id, revoked_ids, ) recovery_txn = json.loads(calculated_txn.to_json()) @@ -631,7 +635,7 @@ async def fix_ledger_entry( async with ledger: ledger_response = await ledger.send_revoc_reg_entry( - rev_list.rev_reg_id, "CL_ACCUM", recovery_txn + rev_list.rev_reg_def_id, "CL_ACCUM", recovery_txn ) applied_txn = ledger_response["result"] diff --git a/aries_cloudagent/anoncreds/models/anoncreds_revocation.py b/aries_cloudagent/anoncreds/models/anoncreds_revocation.py index 1d25b3e26a..c38241cc11 100644 --- a/aries_cloudagent/anoncreds/models/anoncreds_revocation.py +++ b/aries_cloudagent/anoncreds/models/anoncreds_revocation.py @@ -277,20 +277,20 @@ class RevList(BaseModel): class Meta: """RevList metadata.""" - schema_class = "AnonCredsRevocationListSchema" + schema_class = "RevStatusListSchema" def __init__( self, issuer_id: str, - rev_reg_id: str, + rev_reg_def_id: str, revocation_list: List[int], current_accumulator: str, - timestamp: int, + timestamp: Optional[int] = None, **kwargs, ): super().__init__(**kwargs) self.issuer_id = issuer_id - self.rev_reg_id = rev_reg_id + self.rev_reg_def_id = rev_reg_def_id self.revocation_list = revocation_list self.current_accumulator = current_accumulator self.timestamp = timestamp @@ -318,15 +318,20 @@ class Meta: description="Issuer Identifier of the credential definition or schema", data_key="issuerId", ) - rev_reg_id = fields.Str( + rev_reg_def_id = fields.Str( description="", - data_key="revRegId", + data_key="revRegDefId", ) revocation_list = fields.List( - fields.Str(description=""), description="", data_key="revocationList" + fields.Int(), + description="Bit list representing revoked credentials", + data_key="revocationList", ) current_accumulator = fields.Str(data_key="currentAccumulator") - timestamp = fields.Int() + timestamp = fields.Int( + description="Timestamp at which revocation list is applicable", + required=False, + ) class RevListState(BaseModel): From 96ccbe9d1782e9d0dbb856382c81d9e43a4207b6 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Thu, 27 Apr 2023 10:39:23 -0400 Subject: [PATCH 116/150] fix: anoncreds issuer specifies tails dir Signed-off-by: Daniel Bluhm --- aries_cloudagent/anoncreds/issuer.py | 24 ++++++++++--------- .../anoncreds/models/anoncreds_revocation.py | 4 ++-- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/aries_cloudagent/anoncreds/issuer.py b/aries_cloudagent/anoncreds/issuer.py index 3850865346..d7c2f8c668 100644 --- a/aries_cloudagent/anoncreds/issuer.py +++ b/aries_cloudagent/anoncreds/issuer.py @@ -20,6 +20,7 @@ from ..askar.profile import AskarProfile from ..core.error import BaseError +from .base import AnonCredsSchemaAlreadyExists from .models.anoncreds_cred_def import CredDef, CredDefResult, CredDefState from .models.anoncreds_revocation import ( RevRegDef, @@ -28,8 +29,8 @@ RevList, ) from .models.anoncreds_schema import AnonCredsSchema, SchemaResult, SchemaState -from .base import AnonCredsSchemaAlreadyExists from .registry import AnonCredsRegistry +from .util import indy_client_dir LOGGER = logging.getLogger(__name__) @@ -449,7 +450,6 @@ async def create_and_register_revocation_registry_definition( registry_type: str, tag: str, max_cred_num: int, - tails_base_path: str, options: Optional[dict] = None, ) -> RevRegDefResult: """ @@ -481,6 +481,8 @@ async def create_and_register_revocation_registry_definition( "Credential definition not found for revocation registry" ) + tails_dir = indy_client_dir("tails", create=True) + try: ( rev_reg_def, @@ -494,10 +496,10 @@ async def create_and_register_revocation_registry_definition( tag, registry_type, max_cred_num, - tails_dir_path=tails_base_path, + tails_dir_path=tails_dir, ), ) - + # TODO Move tails file to more human friendly folder structure? except AnoncredsError as err: raise AnonCredsIssuerError("Error creating revocation registry") from err @@ -508,7 +510,6 @@ async def create_and_register_revocation_registry_definition( self.profile, RevRegDef.from_native(rev_reg_def), options ) - # rev_reg_def_id = f"{origin_did}:4:{cred_def_id}:CL_ACCUM:{tag}" rev_reg_def_id = result.rev_reg_def_id try: @@ -570,7 +571,7 @@ async def get_created_revocation_registry_definitions( return [entry.name for entry in rev_reg_defs] async def create_and_register_revocation_list( - self, rev_reg_def_id: str, options: dict + self, rev_reg_def_id: str, options: Optional[dict] = None ): """Create and register a revocation list.""" try: @@ -588,24 +589,25 @@ async def create_and_register_revocation_list( f"Revocation registry definition not found for id {rev_reg_def_id}" ) - issuer_id = rev_reg_def_entry.value_json["issuerId"] + rev_reg_def = RevRegDef.deserialize(rev_reg_def_entry.value_json) rev_list = RevocationStatusList.create( rev_reg_def_id, rev_reg_def_entry.raw_value, - issuer_id, + rev_reg_def.issuer_id, ) anoncreds_registry = self.profile.inject(AnonCredsRegistry) result = await anoncreds_registry.register_revocation_list( - self.profile, RevList.from_native(rev_list), options + self.profile, rev_reg_def, RevList.from_native(rev_list), options ) - rev_list_json = rev_list.to_json() try: async with self._profile.session() as session: await session.handle.insert( - CATEGORY_REV_LIST, rev_reg_def_id, rev_list_json + CATEGORY_REV_LIST, + rev_reg_def_id, + result.revocation_list_state.revocation_list.to_json(), ) except AskarError as err: raise AnonCredsIssuerError("Error saving new revocation registry") from err diff --git a/aries_cloudagent/anoncreds/models/anoncreds_revocation.py b/aries_cloudagent/anoncreds/models/anoncreds_revocation.py index c38241cc11..eccc2f9416 100644 --- a/aries_cloudagent/anoncreds/models/anoncreds_revocation.py +++ b/aries_cloudagent/anoncreds/models/anoncreds_revocation.py @@ -396,13 +396,13 @@ def __init__( ): super().__init__(**kwargs) self.job_id = job_id - self.revocations_list_state = revocation_list_state + self.revocation_list_state = revocation_list_state self.registration_metadata = registration_metadata self.revocation_list_metadata = revocation_list_metadata @property def rev_reg_def_id(self): - return self.revocation_list_state.revocation_list_id + return self.revocation_list_state.revocation_list.rev_reg_def_id class RevListResultSchema(BaseModelSchema): From 60431735e044ae7f2c8013e550ba02ac33689136 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Thu, 27 Apr 2023 10:40:03 -0400 Subject: [PATCH 117/150] feat: initial pass at rev list register endpoint Signed-off-by: Daniel Bluhm --- aries_cloudagent/anoncreds/routes.py | 67 +++++++++++++++++-- aries_cloudagent/revocation/manager.py | 19 +++--- .../models/issuer_rev_reg_record.py | 56 ++++++++++++---- .../revocation/models/revocation_registry.py | 6 +- 4 files changed, 118 insertions(+), 30 deletions(-) diff --git a/aries_cloudagent/anoncreds/routes.py b/aries_cloudagent/anoncreds/routes.py index 5a52a93c2b..5dd6af2d51 100644 --- a/aries_cloudagent/anoncreds/routes.py +++ b/aries_cloudagent/anoncreds/routes.py @@ -12,15 +12,18 @@ ) from marshmallow import fields -from aries_cloudagent.revocation.error import RevocationNotSupportedError - from ..admin.request_context import AdminRequestContext from ..messaging.models.openapi import OpenAPISchema from ..messaging.valid import UUIDFour from ..revocation.anoncreds import AnonCredsRevocation -from .issuer import AnonCredsIssuer +from ..revocation.error import RevocationNotSupportedError +from ..storage.error import StorageNotFoundError +from .issuer import AnonCredsIssuer, AnonCredsIssuerError from .models.anoncreds_cred_def import CredDefResultSchema, GetCredDefResultSchema -from .models.anoncreds_revocation import RevRegDefResultSchema +from .models.anoncreds_revocation import ( + RevRegDefResultSchema, + RevStatusListResultSchema, +) from .models.anoncreds_schema import ( AnonCredsSchemaSchema, GetSchemaResultSchema, @@ -382,7 +385,60 @@ async def rev_reg_def_post(request: web.BaseRequest): ) except RevocationNotSupportedError as e: raise web.HTTPBadRequest(reason=e.message) from e - result = await shield(issuer_rev_reg_rec.generate_and_publish(context.profile)) + result = await shield(issuer_rev_reg_rec.create_and_register_def(context.profile)) + + return web.json_response(result.serialize()) + + +class RevStatusListCreateRequestSchema(OpenAPISchema): + """Request schema for revocation registry creation request.""" + + rev_reg_def_id = fields.Str( + description="Revocation registry definition identifier", + data_key="revRegDefId", + ) + + +@docs(tags=["anoncreds"], summary="") +@request_schema(RevStatusListCreateRequestSchema()) +@response_schema(RevStatusListResultSchema(), 200, description="") +async def rev_status_list_post(request: web.BaseRequest): + """Request handler for creating . + + Args: + + + (method) def create_and_register_revocation_registry_definition( + issuer_id: str, + cred_def_id: str, + tag: str, + max_cred_num: int, + registry_type: str, + tails_base_path: str, + options: dict[Unknown, Unknown] | None = None + ) -> Coroutine[Any, Any, RevRegDefResult] + + Returns: + + """ + context: AdminRequestContext = request["context"] + body = await request.json() + rev_reg_def_id = body.get("revRegDefId") + options = body.get("options") + + try: + revoc = AnonCredsRevocation(context.profile) + rev_reg = await revoc.get_issuer_rev_reg_record(rev_reg_def_id) + result = await rev_reg.create_and_register_list( + context.profile, + options, + ) + LOGGER.debug("published revocation list for: %s", rev_reg_def_id) + + except StorageNotFoundError as err: + raise web.HTTPNotFound(reason=err.roll_up) from err + except AnonCredsIssuerError as err: + raise web.HTTPBadRequest(reason=err.roll_up) from err return web.json_response(result.serialize()) @@ -407,6 +463,7 @@ async def register(app: web.Application): allow_head=False, ), web.post("/anoncreds/revocation-registry-definition", rev_reg_def_post), + web.post("/anoncreds/revocation-status-list", rev_status_list_post), ] ) diff --git a/aries_cloudagent/revocation/manager.py b/aries_cloudagent/revocation/manager.py index a5e3927f5d..34b9f380b2 100644 --- a/aries_cloudagent/revocation/manager.py +++ b/aries_cloudagent/revocation/manager.py @@ -1,6 +1,5 @@ """Classes to manage credential revocation.""" -import json import logging from typing import Mapping, Sequence, Text @@ -134,19 +133,20 @@ async def revoke_credential( await rev_reg.get_or_fetch_local_tails_path() # pick up pending revocations on input revocation registry crids = (issuer_rr_rec.pending_pub or []) + [cred_rev_id] - (delta_json, _) = await issuer.revoke_credentials( + (prev, curr, _) = await issuer.revoke_credentials( issuer_rr_rec.revoc_reg_id, issuer_rr_rec.tails_local_path, crids ) async with self._profile.transaction() as txn: issuer_rr_upd = await IssuerRevRegRecord.retrieve_by_id( txn, issuer_rr_rec.record_id, for_update=True ) - if delta_json: - issuer_rr_upd.revoc_reg_entry = json.loads(delta_json) + if prev: + issuer_rr_upd.prev_status_list = prev + issuer_rr_upd.rev_status_list = curr await issuer_rr_upd.clear_pending(txn, crids) await txn.commit() await self.set_cred_revoked_state(rev_reg_id, crids) - if delta_json: + if prev: await issuer_rr_upd.send_entry(self._profile) await notify_revocation_published_event( self._profile, rev_reg_id, [cred_rev_id] @@ -225,7 +225,7 @@ async def publish_pending_revocations( if limit_crids: crids = crids.intersection(limit_crids) if crids: - (delta_json, failed_crids) = await issuer.revoke_credentials( + (prev, curr, failed_crids) = await issuer.revoke_credentials( issuer_rr_rec.revoc_reg_id, issuer_rr_rec.tails_local_path, crids, @@ -234,12 +234,13 @@ async def publish_pending_revocations( issuer_rr_upd = await IssuerRevRegRecord.retrieve_by_id( txn, issuer_rr_rec.record_id, for_update=True ) - if delta_json: - issuer_rr_upd.revoc_reg_entry = json.loads(delta_json) + if prev: + issuer_rr_upd.prev_status_list = prev + issuer_rr_upd.rev_status_list = curr await issuer_rr_upd.clear_pending(txn, crids) await txn.commit() await self.set_cred_revoked_state(issuer_rr_rec.revoc_reg_id, crids) - if delta_json: + if prev: await issuer_rr_upd.send_entry(self._profile) published = sorted(crid for crid in crids if crid not in failed_crids) result[issuer_rr_rec.revoc_reg_id] = published diff --git a/aries_cloudagent/revocation/models/issuer_rev_reg_record.py b/aries_cloudagent/revocation/models/issuer_rev_reg_record.py index 4d826124b7..f568c9d1a6 100644 --- a/aries_cloudagent/revocation/models/issuer_rev_reg_record.py +++ b/aries_cloudagent/revocation/models/issuer_rev_reg_record.py @@ -165,7 +165,7 @@ def _check_url(self, url) -> None: if not (parsed.scheme and parsed.netloc and parsed.path): raise RevocationError("URI {} is not a valid URL".format(url)) - async def generate_and_publish(self, profile: Profile): + async def create_and_register_def(self, profile: Profile): """Create the revocation registry definition and tails file and publish it.""" if not self.tag: self.tag = self._id or str(uuid.uuid4()) @@ -179,8 +179,6 @@ async def generate_and_publish(self, profile: Profile): issuer = AnonCredsIssuer(profile) - tails_hopper_dir = indy_client_dir(join("tails", ".hopper"), create=True) - LOGGER.debug("Creating revocation registry with size: %d", self.max_cred_num) try: @@ -190,7 +188,6 @@ async def generate_and_publish(self, profile: Profile): self.revoc_def_type, self.tag, self.max_cred_num, - tails_hopper_dir, self.options, ) except AnonCredsIssuerError as err: @@ -201,18 +198,14 @@ async def generate_and_publish(self, profile: Profile): self.state = IssuerRevRegRecord.STATE_POSTED self.tails_hash = result.rev_reg_def.value.tails_hash self.tails_public_uri = result.rev_reg_def.value.tails_location - - tails_dir = indy_client_dir(join("tails", self.revoc_reg_id), create=True) - tails_path = join(tails_dir, self.tails_hash) - move(join(tails_hopper_dir, self.tails_hash), tails_path) - self.tails_local_path = tails_path + self.tails_local_path = result.rev_reg_def.value.tails_location async with profile.session() as session: await self.save(session, reason="Generated registry") return result - # TODO Delete me; replaced by generate_and_publish + # TODO Delete me; replaced by create_and_register_def async def generate_registry(self, profile: Profile): """Create the revocation registry definition and tails file.""" if not self.tag: @@ -263,7 +256,7 @@ async def generate_registry(self, profile: Profile): async with profile.session() as session: await self.save(session, reason="Generated registry") - # TODO Delete me? Probably not needed anymore with the generate_and_publish + # TODO Delete me? Probably not needed anymore with the create_and_register_def async def set_tails_file_public_uri(self, profile: Profile, tails_file_uri: str): """Update tails file's publicly accessible URI.""" if not (self.revoc_reg_def and self.revoc_reg_def.value.tails_location): @@ -277,7 +270,7 @@ async def set_tails_file_public_uri(self, profile: Profile, tails_file_uri: str) async with profile.session() as session: await self.save(session, reason="Set tails file public URI") - # TODO Delete me; replaced by generate_and_publish + # TODO Delete me; replaced by create_and_register_def async def send_def( self, profile: Profile, @@ -360,7 +353,44 @@ async def send_entry( return rev_entry_res - # TODO Update to align with rev list (if that's even necessary; does + async def create_and_register_list( + self, + profile: Profile, + options: Optional[dict] = None, + write_ledger: bool = True, # TODO Delete me + endorser_did: str = None, # TODO Delete me + ) -> RevListResult: + """Send a registry entry to the ledger.""" + if not (self.revoc_reg_id and self.revoc_def_type and self.issuer_id): + raise RevocationError("Revocation registry undefined") + + self._check_url(self.tails_public_uri) + + if self.state not in (IssuerRevRegRecord.STATE_POSTED,): + raise RevocationError( + "Revocation registry {} in state {}: cannot publish entry".format( + self.revoc_reg_id, self.state + ) + ) + + issuer = AnonCredsIssuer(profile) + result = await issuer.create_and_register_revocation_list( + self.revoc_reg_id, + options, + ) + + if self.state == IssuerRevRegRecord.STATE_POSTED: + self.state = ( + IssuerRevRegRecord.STATE_ACTIVE + ) # registering rev status list activates + async with profile.session() as session: + await self.save( + session, reason="Published initial revocation registry entry" + ) + + return result + + # TODO Update to align with rev status list (if that's even necessary; does # this move to indy registry?) async def fix_ledger_entry( self, diff --git a/aries_cloudagent/revocation/models/revocation_registry.py b/aries_cloudagent/revocation/models/revocation_registry.py index b8e1cf461d..14ddcb5abb 100644 --- a/aries_cloudagent/revocation/models/revocation_registry.py +++ b/aries_cloudagent/revocation/models/revocation_registry.py @@ -41,7 +41,7 @@ def __init__( ): """Initialize the revocation registry instance.""" self._cred_def_id = cred_def_id - self.issuer_id = issuer_id + self._issuer_id = issuer_id self._max_creds = max_creds self._reg_def_type = reg_def_type self._registry_id = registry_id @@ -89,7 +89,7 @@ def cred_def_id(self) -> str: @property def issuer_id(self) -> str: """Accessor for the issuer DID.""" - return self.issuer_id + return self._issuer_id @property def max_creds(self) -> int: @@ -146,7 +146,7 @@ def get_receiving_tails_local_path(self): if self._tails_local_path: return self._tails_local_path - tails_dir = indy_client_dir(join("tails", self.registry_id), create=False) + tails_dir = indy_client_dir("tails", create=False) return join(tails_dir, self._tails_hash) def has_local_tails_file(self) -> bool: From 1178edf9ae3baf97bb12dad530e1ed2fd2eb7d7c Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Thu, 27 Apr 2023 16:08:36 -0400 Subject: [PATCH 118/150] fix: rev status list to rev list Signed-off-by: Daniel Bluhm --- .../anoncreds/models/anoncreds_revocation.py | 2 +- aries_cloudagent/anoncreds/routes.py | 12 ++++++------ aries_cloudagent/revocation/manager.py | 8 ++++---- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/aries_cloudagent/anoncreds/models/anoncreds_revocation.py b/aries_cloudagent/anoncreds/models/anoncreds_revocation.py index eccc2f9416..002daa997a 100644 --- a/aries_cloudagent/anoncreds/models/anoncreds_revocation.py +++ b/aries_cloudagent/anoncreds/models/anoncreds_revocation.py @@ -277,7 +277,7 @@ class RevList(BaseModel): class Meta: """RevList metadata.""" - schema_class = "RevStatusListSchema" + schema_class = "RevListSchema" def __init__( self, diff --git a/aries_cloudagent/anoncreds/routes.py b/aries_cloudagent/anoncreds/routes.py index 5dd6af2d51..dbf913c296 100644 --- a/aries_cloudagent/anoncreds/routes.py +++ b/aries_cloudagent/anoncreds/routes.py @@ -22,7 +22,7 @@ from .models.anoncreds_cred_def import CredDefResultSchema, GetCredDefResultSchema from .models.anoncreds_revocation import ( RevRegDefResultSchema, - RevStatusListResultSchema, + RevListResultSchema, ) from .models.anoncreds_schema import ( AnonCredsSchemaSchema, @@ -390,7 +390,7 @@ async def rev_reg_def_post(request: web.BaseRequest): return web.json_response(result.serialize()) -class RevStatusListCreateRequestSchema(OpenAPISchema): +class RevListCreateRequestSchema(OpenAPISchema): """Request schema for revocation registry creation request.""" rev_reg_def_id = fields.Str( @@ -400,9 +400,9 @@ class RevStatusListCreateRequestSchema(OpenAPISchema): @docs(tags=["anoncreds"], summary="") -@request_schema(RevStatusListCreateRequestSchema()) -@response_schema(RevStatusListResultSchema(), 200, description="") -async def rev_status_list_post(request: web.BaseRequest): +@request_schema(RevListCreateRequestSchema()) +@response_schema(RevListResultSchema(), 200, description="") +async def rev_list_post(request: web.BaseRequest): """Request handler for creating . Args: @@ -463,7 +463,7 @@ async def register(app: web.Application): allow_head=False, ), web.post("/anoncreds/revocation-registry-definition", rev_reg_def_post), - web.post("/anoncreds/revocation-status-list", rev_status_list_post), + web.post("/anoncreds/revocation-status-list", rev_list_post), ] ) diff --git a/aries_cloudagent/revocation/manager.py b/aries_cloudagent/revocation/manager.py index 34b9f380b2..72f0a210ba 100644 --- a/aries_cloudagent/revocation/manager.py +++ b/aries_cloudagent/revocation/manager.py @@ -141,8 +141,8 @@ async def revoke_credential( txn, issuer_rr_rec.record_id, for_update=True ) if prev: - issuer_rr_upd.prev_status_list = prev - issuer_rr_upd.rev_status_list = curr + issuer_rr_upd.prev_list = prev + issuer_rr_upd.rev_list = curr await issuer_rr_upd.clear_pending(txn, crids) await txn.commit() await self.set_cred_revoked_state(rev_reg_id, crids) @@ -235,8 +235,8 @@ async def publish_pending_revocations( txn, issuer_rr_rec.record_id, for_update=True ) if prev: - issuer_rr_upd.prev_status_list = prev - issuer_rr_upd.rev_status_list = curr + issuer_rr_upd.prev_list = prev + issuer_rr_upd.rev_list = curr await issuer_rr_upd.clear_pending(txn, crids) await txn.commit() await self.set_cred_revoked_state(issuer_rr_rec.revoc_reg_id, crids) From cfcd24be30074f2e42080f2a9cb623acd2b2fd4b Mon Sep 17 00:00:00 2001 From: Char Howland Date: Thu, 27 Apr 2023 13:49:49 -0600 Subject: [PATCH 119/150] fix: remove status from anoncreds/revocation-list endpoint Signed-off-by: Char Howland --- aries_cloudagent/anoncreds/routes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aries_cloudagent/anoncreds/routes.py b/aries_cloudagent/anoncreds/routes.py index dbf913c296..e417b9e29f 100644 --- a/aries_cloudagent/anoncreds/routes.py +++ b/aries_cloudagent/anoncreds/routes.py @@ -463,7 +463,7 @@ async def register(app: web.Application): allow_head=False, ), web.post("/anoncreds/revocation-registry-definition", rev_reg_def_post), - web.post("/anoncreds/revocation-status-list", rev_list_post), + web.post("/anoncreds/revocation-list", rev_list_post), ] ) From 2fcd5f1ed99b3f05b1f95c39f6898f1b51dede70 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Tue, 2 May 2023 14:49:03 -0600 Subject: [PATCH 120/150] fix: remove url check for tails_public_uri Signed-off-by: Char Howland --- aries_cloudagent/revocation/models/issuer_rev_reg_record.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/aries_cloudagent/revocation/models/issuer_rev_reg_record.py b/aries_cloudagent/revocation/models/issuer_rev_reg_record.py index f568c9d1a6..cdfe47a191 100644 --- a/aries_cloudagent/revocation/models/issuer_rev_reg_record.py +++ b/aries_cloudagent/revocation/models/issuer_rev_reg_record.py @@ -364,8 +364,6 @@ async def create_and_register_list( if not (self.revoc_reg_id and self.revoc_def_type and self.issuer_id): raise RevocationError("Revocation registry undefined") - self._check_url(self.tails_public_uri) - if self.state not in (IssuerRevRegRecord.STATE_POSTED,): raise RevocationError( "Revocation registry {} in state {}: cannot publish entry".format( From a2bb0c2d2de249c56ff71e948622482c107c54ac Mon Sep 17 00:00:00 2001 From: Char Howland Date: Wed, 3 May 2023 11:58:23 -0600 Subject: [PATCH 121/150] fix: update init_issuer_registry calls to include issuer_id argument Signed-off-by: Char Howland --- aries_cloudagent/messaging/credential_definitions/routes.py | 1 + aries_cloudagent/revocation/anoncreds.py | 1 + aries_cloudagent/revocation/routes.py | 3 +++ 3 files changed, 5 insertions(+) diff --git a/aries_cloudagent/messaging/credential_definitions/routes.py b/aries_cloudagent/messaging/credential_definitions/routes.py index e09d9b0c81..5d510216e4 100644 --- a/aries_cloudagent/messaging/credential_definitions/routes.py +++ b/aries_cloudagent/messaging/credential_definitions/routes.py @@ -521,6 +521,7 @@ async def on_cred_def_event(profile: Profile, event: Event): # without a delay revoc = AnonCredsRevocation(profile) await revoc.init_issuer_registry( + issuer_did, cred_def_id, rev_reg_size, create_pending_rev_reg=create_pending_rev_reg, diff --git a/aries_cloudagent/revocation/anoncreds.py b/aries_cloudagent/revocation/anoncreds.py index 03a513b942..bcf4612d98 100644 --- a/aries_cloudagent/revocation/anoncreds.py +++ b/aries_cloudagent/revocation/anoncreds.py @@ -101,6 +101,7 @@ async def handle_full_registry(self, revoc_reg_id: str): await txn.commit() await self.init_issuer_registry( + registry.issuer_id, registry.cred_def_id, registry.max_cred_num, registry.revoc_def_type, diff --git a/aries_cloudagent/revocation/routes.py b/aries_cloudagent/revocation/routes.py index 2dc5ca428d..c16b4279de 100644 --- a/aries_cloudagent/revocation/routes.py +++ b/aries_cloudagent/revocation/routes.py @@ -569,6 +569,7 @@ async def create_rev_reg(request: web.BaseRequest): try: revoc = AnonCredsRevocation(profile) issuer_rev_reg_rec = await revoc.init_issuer_registry( + # TODO: pass in issuer_id credential_definition_id, max_cred_num=max_cred_num, notify=False, @@ -1380,6 +1381,7 @@ async def generate(rr_record: IssuerRevRegRecord) -> dict: if write_ledger and create_pending_rev_reg: revoc = AnonCredsRevocation(profile) await revoc.init_issuer_registry( + registry_record.issuer_id, registry_record.cred_def_id, registry_record.max_cred_num, registry_record.revoc_def_type, @@ -1483,6 +1485,7 @@ async def on_revocation_registry_endorsed_event(profile: Profile, event: Event): else None ) await revoc.init_issuer_registry( + registry_record.issuer_id, registry_record.cred_def_id, registry_record.max_cred_num, registry_record.revoc_def_type, From 273600019710421a10e24879f072ff30c349369b Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Thu, 4 May 2023 15:42:25 -0400 Subject: [PATCH 122/150] feat: add generic tails file upload Signed-off-by: Daniel Bluhm --- .../anoncreds/default/legacy_indy/registry.py | 25 +---- aries_cloudagent/anoncreds/issuer.py | 101 ++++++++++++++---- aries_cloudagent/config/default_context.py | 2 +- .../models/issuer_rev_reg_record.py | 2 +- .../tails/anoncreds_tails_server.py | 64 +++++++++++ aries_cloudagent/tails/base.py | 9 +- aries_cloudagent/tails/indy_tails_server.py | 10 +- 7 files changed, 163 insertions(+), 50 deletions(-) create mode 100644 aries_cloudagent/tails/anoncreds_tails_server.py diff --git a/aries_cloudagent/anoncreds/default/legacy_indy/registry.py b/aries_cloudagent/anoncreds/default/legacy_indy/registry.py index c1ae812bde..e2c730d736 100644 --- a/aries_cloudagent/anoncreds/default/legacy_indy/registry.py +++ b/aries_cloudagent/anoncreds/default/legacy_indy/registry.py @@ -4,7 +4,6 @@ import logging import re from typing import Optional, Pattern, Tuple -from urllib.parse import urlparse from ....config.injection_context import InjectionContext from ....core.profile import Profile @@ -21,7 +20,6 @@ ) from ....multitenant.base import BaseMultitenantManager from ....revocation.anoncreds import AnonCredsRevocation -from ....revocation.error import RevocationError from ....revocation.models.issuer_cred_rev_record import IssuerCredRevRecord from ....revocation.recover import generate_ledger_rrrecovery_txn from ....storage.error import StorageNotFoundError @@ -374,11 +372,6 @@ async def get_revocation_registry_definition( async def get_revocation_registry_definitions(self, profile: Profile, filter: str): """Get credential definition ids filtered by filter""" - def _check_url(self, url) -> None: - parsed = urlparse(url) - if not (parsed.scheme and parsed.netloc and parsed.path): - raise RevocationError("URI {} is not a valid URL".format(url)) - async def register_revocation_registry_definition( self, profile: Profile, @@ -387,14 +380,7 @@ async def register_revocation_registry_definition( ) -> RevRegDefResult: """Register a revocation registry definition on the registry.""" - tails_base_url = profile.settings.get("tails_server_base_url") - if not tails_base_url: - raise AnonCredsRegistrationError("tails_server_base_url not configured") - rev_reg_def_id = self.make_rev_reg_def_id(revocation_registry_definition) - revocation_registry_definition.value.tails_location = ( - tails_base_url.rstrip("/") + f"/{rev_reg_def_id}" - ) try: self._check_url(revocation_registry_definition.value.tails_location) @@ -474,7 +460,7 @@ async def _revoc_reg_entry_with_fix( profile, rev_list, True, - ledger.pool.genesis_txns, + ledger.genesis_txns, ) rev_entry_res = {"result": res} LOGGER.warn("Ledger update/fix applied") @@ -504,10 +490,7 @@ async def register_revocation_list( options: Optional[dict] = None, ) -> RevListResult: """Register a revocation list on the registry.""" - rev_reg_entry = { - "ver": "1.0", - "value": {"accum": rev_list.current_accumulator} - } + rev_reg_entry = {"ver": "1.0", "value": {"accum": rev_list.current_accumulator}} rev_entry_res = await self._revoc_reg_entry_with_fix( profile, rev_list, rev_reg_def.type, rev_reg_entry @@ -578,7 +561,9 @@ async def fix_ledger_entry( # get rev reg delta (revocations published to ledger) ledger = profile.inject(BaseLedger) async with ledger: - (rev_reg_delta, _) = await ledger.get_revoc_reg_delta(rev_list.rev_reg_def_id) + (rev_reg_delta, _) = await ledger.get_revoc_reg_delta( + rev_list.rev_reg_def_id + ) # get rev reg records from wallet (revocations and list) recs = [] diff --git a/aries_cloudagent/anoncreds/issuer.py b/aries_cloudagent/anoncreds/issuer.py index d7c2f8c668..232bca8e94 100644 --- a/aries_cloudagent/anoncreds/issuer.py +++ b/aries_cloudagent/anoncreds/issuer.py @@ -2,8 +2,11 @@ import asyncio import logging +import os +from pathlib import Path from time import time from typing import NamedTuple, Optional, Sequence, Tuple +from urllib.parse import urlparse from anoncreds import ( AnoncredsError, @@ -20,13 +23,14 @@ from ..askar.profile import AskarProfile from ..core.error import BaseError -from .base import AnonCredsSchemaAlreadyExists +from ..tails.base import BaseTailsServer +from .base import AnonCredsRegistrationError, AnonCredsSchemaAlreadyExists from .models.anoncreds_cred_def import CredDef, CredDefResult, CredDefState from .models.anoncreds_revocation import ( + RevList, RevRegDef, RevRegDefResult, RevRegDefState, - RevList, ) from .models.anoncreds_schema import AnonCredsSchema, SchemaResult, SchemaState from .registry import AnonCredsRegistry @@ -36,10 +40,10 @@ DEFAULT_CRED_DEF_TAG = "default" DEFAULT_SIGNATURE_TYPE = "CL" +CATEGORY_SCHEMA = "schema" CATEGORY_CRED_DEF = "credential_def" CATEGORY_CRED_DEF_PRIVATE = "credential_def_private" CATEGORY_CRED_DEF_KEY_PROOF = "credential_def_key_proof" -CATEGORY_SCHEMA = "schema" CATEGORY_REV_LIST = "revocation_list" CATEGORY_REV_REG_INFO = "revocation_reg_info" CATEGORY_REV_REG_DEF = "revocation_reg_def" @@ -104,7 +108,7 @@ async def _update_entry_state(self, category: str, name: str, state: str): async def _store_schema( self, - schema_id: str, + record_id: str, schema: AnonCredsSchema, state: str, ): @@ -113,7 +117,7 @@ async def _store_schema( async with self._profile.session() as session: await session.handle.insert( CATEGORY_SCHEMA, - schema_id, + record_id, schema.to_json(), { "name": schema.name, @@ -175,7 +179,7 @@ async def create_and_register_schema( ) await self._store_schema( - schema_result.schema_state.schema_id, + schema_result.schema_state.schema_id or schema_result.job_id, schema_result.schema_state.schema, state=schema_result.schema_state.state, ) @@ -273,14 +277,14 @@ async def create_and_register_credential_definition( Create a new credential definition and store it in the wallet. Args: - origin_did: the DID issuing the credential definition - schema_json: the schema used as a basis - signature_type: the credential definition signature type (default 'CL') - tag: the credential definition tag - support_revocation: whether to enable revocation for this credential def + issuer_id: the ID of the issuer creating the credential definition + schema_id: the schema ID for the credential definition + tag: the tag to use for the credential definition + signature_type: the signature type to use for the credential definition + options: any additional options to use when creating the credential definition Returns: - A tuple of the credential definition ID and JSON + CredDefResult: the result of the credential definition creation """ anoncreds_registry = self._profile.inject(AnonCredsRegistry) @@ -453,19 +457,18 @@ async def create_and_register_revocation_registry_definition( options: Optional[dict] = None, ) -> RevRegDefResult: """ - Create a new revocation registry and store it in the wallet. + Create a new revocation registry and register on network. Args: - origin_did: the DID issuing the revocation registry - cred_def_id: the identifier of the related credential definition - revoc_def_type: the revocation registry type (default CL_ACCUM) - tag: the unique revocation registry tag - max_cred_num: the number of credentials supported in the registry - tails_base_path: where to store the tails file - issuance_type: optionally override the issuance type + issuer_id (str): issuer identifier + cred_def_id (str): credential definition identifier + registry_type (str): revocation registry type + tag (str): revocation registry tag + max_cred_num (int): maximum number of credentials supported + options (dict): revocation registry options Returns: - A tuple of the revocation registry ID, JSON, and entry JSON + RevRegDefResult: revocation registry definition result """ try: @@ -499,15 +502,17 @@ async def create_and_register_revocation_registry_definition( tails_dir_path=tails_dir, ), ) - # TODO Move tails file to more human friendly folder structure? except AnoncredsError as err: raise AnonCredsIssuerError("Error creating revocation registry") from err rev_reg_def_json = rev_reg_def.to_json() + rev_reg_def = RevRegDef.from_native(rev_reg_def) + public_tails_uri = self.get_public_tails_uri(rev_reg_def) + rev_reg_def.value.tails_location = public_tails_uri anoncreds_registry = self.profile.inject(AnonCredsRegistry) result = await anoncreds_registry.register_revocation_registry_definition( - self.profile, RevRegDef.from_native(rev_reg_def), options + self.profile, rev_reg_def, options ) rev_reg_def_id = result.rev_reg_def_id @@ -536,6 +541,56 @@ async def create_and_register_revocation_registry_definition( return result + def _check_url(self, url) -> None: + parsed = urlparse(url) + if not (parsed.scheme and parsed.netloc and parsed.path): + raise AnonCredsRegistrationError("URI {} is not a valid URL".format(url)) + + def get_public_tails_uri(self, rev_reg_def: RevRegDef): + """Construct tails uri from rev_reg_def.""" + tails_base_url = self._profile.settings.get("tails_server_base_url") + if not tails_base_url: + raise AnonCredsRegistrationError("tails_server_base_url not configured") + + public_tails_uri = ( + tails_base_url.rstrip("/") + f"/{rev_reg_def.value.tails_hash}" + ) + + self._check_url(public_tails_uri) + return public_tails_uri + + def get_local_tails_path(self, rev_reg_def: RevRegDef) -> str: + """Get the local path to the tails file.""" + tails_dir = indy_client_dir("tails", create=False) + return os.path.join(tails_dir, rev_reg_def.value.tails_hash) + + 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) + if not tails_server: + raise AnonCredsIssuerError("Tails server not configured") + if not Path(self.get_local_tails_path(rev_reg_def)).is_file(): + raise AnonCredsIssuerError("Local tails file not found") + + (upload_success, result) = await tails_server.upload_tails_file( + self._profile.context, + rev_reg_def.value.tails_hash, + self.get_local_tails_path(rev_reg_def), + interval=0.8, + backoff=-0.5, + max_attempts=5, # heuristic: respect HTTP timeout + ) + if not upload_success: + raise AnonCredsIssuerError( + f"Tails file for rev reg for {rev_reg_def.cred_def_id} " + "failed to upload: {result}" + ) + if rev_reg_def.value.tails_location != result: + raise AnonCredsIssuerError( + f"Tails file for rev reg for {rev_reg_def.cred_def_id} " + "uploaded to wrong location: {result}" + ) + async def update_revocation_registry_definition_state( self, rev_reg_def_id: str, state: str ): diff --git a/aries_cloudagent/config/default_context.py b/aries_cloudagent/config/default_context.py index 8cb951c4d4..e9dfd85599 100644 --- a/aries_cloudagent/config/default_context.py +++ b/aries_cloudagent/config/default_context.py @@ -84,7 +84,7 @@ async def bind_providers(self, context: InjectionContext): context.injector.bind_provider( BaseTailsServer, ClassProvider( - "aries_cloudagent.tails.indy_tails_server.IndyTailsServer", + "aries_cloudagent.tails.anoncreds_tails_server.AnonCredsTailsServer", ), ) diff --git a/aries_cloudagent/revocation/models/issuer_rev_reg_record.py b/aries_cloudagent/revocation/models/issuer_rev_reg_record.py index cdfe47a191..0951355509 100644 --- a/aries_cloudagent/revocation/models/issuer_rev_reg_record.py +++ b/aries_cloudagent/revocation/models/issuer_rev_reg_record.py @@ -198,7 +198,7 @@ async def create_and_register_def(self, profile: Profile): self.state = IssuerRevRegRecord.STATE_POSTED self.tails_hash = result.rev_reg_def.value.tails_hash self.tails_public_uri = result.rev_reg_def.value.tails_location - self.tails_local_path = result.rev_reg_def.value.tails_location + self.tails_local_path = issuer.get_local_tails_path(result.rev_reg_def) async with profile.session() as session: await self.save(session, reason="Generated registry") diff --git a/aries_cloudagent/tails/anoncreds_tails_server.py b/aries_cloudagent/tails/anoncreds_tails_server.py new file mode 100644 index 0000000000..bb175dbfc9 --- /dev/null +++ b/aries_cloudagent/tails/anoncreds_tails_server.py @@ -0,0 +1,64 @@ +"""AnonCreds tails server interface class.""" + +import logging + +from typing import Tuple + +from ..config.injection_context import InjectionContext +from ..utils.http import put_file, PutError + +from .base import BaseTailsServer +from .error import TailsServerNotConfiguredError + + +LOGGER = logging.getLogger(__name__) + + +class AnonCredsTailsServer(BaseTailsServer): + """AnonCreds tails server interface.""" + + async def upload_tails_file( + self, + context: InjectionContext, + filename: str, + tails_file_path: str, + interval: float = 1.0, + backoff: float = 0.25, + max_attempts: int = 5, + ) -> Tuple[bool, str]: + """Upload tails file to tails server. + + Args: + context: context with configuration settings + filename: file name given to tails server + tails_file_path: path to the tails file to upload + interval: initial interval between attempts + backoff: exponential backoff in retry interval + max_attempts: maximum number of attempts to make + + Returns: + Tuple[bool, str]: tuple with success status and url of uploaded + file or error message if failed + """ + tails_server_upload_url = context.settings.get("tails_server_upload_url") + + if not tails_server_upload_url: + raise TailsServerNotConfiguredError( + "tails_server_upload_url setting is not set" + ) + + upload_url = tails_server_upload_url.rstrip("/") + f"/{filename}" + + try: + await put_file( + upload_url, + {"tails": tails_file_path}, + {}, + interval=interval, + backoff=backoff, + max_attempts=max_attempts, + ) + except PutError as x_put: + return (False, x_put.message) + + return True, upload_url diff --git a/aries_cloudagent/tails/base.py b/aries_cloudagent/tails/base.py index 7d4b82186b..f3c56f4527 100644 --- a/aries_cloudagent/tails/base.py +++ b/aries_cloudagent/tails/base.py @@ -13,7 +13,7 @@ class BaseTailsServer(ABC, metaclass=ABCMeta): async def upload_tails_file( self, context: InjectionContext, - rev_reg_id: str, + filename: str, tails_file_path: str, interval: float = 1.0, backoff: float = 0.25, @@ -22,9 +22,14 @@ async def upload_tails_file( """Upload tails file to tails server. Args: - rev_reg_id: The revocation registry identifier + context: context with configuration settings + filename: file name given to tails server tails_file: The path to the tails file to upload interval: initial interval between attempts backoff: exponential backoff in retry interval max_attempts: maximum number of attempts to make + + Returns: + Tuple[bool, str]: tuple with success status and url of uploaded + file or error message if failed """ diff --git a/aries_cloudagent/tails/indy_tails_server.py b/aries_cloudagent/tails/indy_tails_server.py index 0c5ebb6ab4..0b6d85e734 100644 --- a/aries_cloudagent/tails/indy_tails_server.py +++ b/aries_cloudagent/tails/indy_tails_server.py @@ -21,7 +21,7 @@ class IndyTailsServer(BaseTailsServer): async def upload_tails_file( self, context: InjectionContext, - rev_reg_id: str, + filename: str, tails_file_path: str, interval: float = 1.0, backoff: float = 0.25, @@ -31,11 +31,15 @@ async def upload_tails_file( Args: context: context with configuration settings - rev_reg_id: revocation registry identifier + filename: file name given to tails server tails_file_path: path to the tails file to upload interval: initial interval between attempts backoff: exponential backoff in retry interval max_attempts: maximum number of attempts to make + + Returns: + Tuple[bool, str]: tuple with success status and url of uploaded + file or error message if failed """ tails_server_upload_url = context.settings.get("tails_server_upload_url") genesis_transactions = context.settings.get("ledger.genesis_transactions") @@ -59,7 +63,7 @@ async def upload_tails_file( "tails_server_upload_url setting is not set" ) - upload_url = tails_server_upload_url.rstrip("/") + f"/{rev_reg_id}" + upload_url = tails_server_upload_url.rstrip("/") + f"/{filename}" try: await put_file( From 6c9dc1a17caab773deb975a1e15bade5055801ac Mon Sep 17 00:00:00 2001 From: Char Howland Date: Fri, 5 May 2023 11:26:08 -0600 Subject: [PATCH 123/150] feat: update anoncreds-rs build Signed-off-by: Char Howland --- docker/Dockerfile.run | 2 +- requirements.anoncreds.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/Dockerfile.run b/docker/Dockerfile.run index 614cb805e7..67a3c28f41 100644 --- a/docker/Dockerfile.run +++ b/docker/Dockerfile.run @@ -17,7 +17,7 @@ RUN pip3 install --no-cache-dir \ -r requirements.bbs.txt \ -r requirements.dev.txt \ -r requirements.anoncreds.txt -RUN curl -sL https://github.com/Indicio-tech/anoncreds-rs/releases/download/v0.1.0-dev.9/library-linux-x86_64.tar.gz | tar -xz -C /usr/local/lib/python3.9/site-packages/anoncreds/ +RUN curl -sL https://github.com/hyperledger/anoncreds-rs/releases/download/v0.1.0-dev.15/library-linux-x86_64.tar.gz | tar -xz -C /usr/local/lib/python3.9/site-packages/anoncreds/ RUN mkdir aries_cloudagent && touch aries_cloudagent/__init__.py ADD aries_cloudagent/version.py aries_cloudagent/version.py diff --git a/requirements.anoncreds.txt b/requirements.anoncreds.txt index e684770d5f..ac3e09e3cc 100644 --- a/requirements.anoncreds.txt +++ b/requirements.anoncreds.txt @@ -1 +1 @@ -anoncreds@git+https://github.com/indicio-tech/anoncreds-rs@feature/python-rev-status-lists#subdirectory=wrappers/python +anoncreds@git+https://github.com/hyperledger/anoncreds-rs@3eca56d#subdirectory=wrappers/python \ No newline at end of file From 40f065b156054ef038d35567cb39309a81beb493 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Fri, 5 May 2023 14:38:49 -0600 Subject: [PATCH 124/150] feat: update image for tails-server Signed-off-by: Char Howland --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index acbbb3edc4..5f1423ef2b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -61,7 +61,7 @@ services: tails: platform: linux/amd64 - image: ghcr.io/bcgov/tails-server:latest + image: ghcr.io/indicio-tech/tails-server:sha-d45f581 ports: - 6543:6543 environment: From aa569316ddcfddd3153c10680801192281453040 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Fri, 5 May 2023 14:41:09 -0600 Subject: [PATCH 125/150] fix: remove status list from revocation-list endpoint Signed-off-by: Char Howland --- anoncreds_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/anoncreds_test.py b/anoncreds_test.py index 2c407a8d19..7845159ded 100644 --- a/anoncreds_test.py +++ b/anoncreds_test.py @@ -65,7 +65,7 @@ async def main(): }, ) rev_status_list = await alice.post( - "/anoncreds/revocation-status-list", + "/anoncreds/revocation-list", json={ "revRegDefId": rev_reg_def["revocation_registry_definition_state"][ "revocation_registry_definition_id" From bad0784a575e37308d7bd921c35a361e7d5ff906 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Fri, 5 May 2023 14:43:08 -0600 Subject: [PATCH 126/150] fix: remove url check Signed-off-by: Char Howland --- aries_cloudagent/anoncreds/default/legacy_indy/registry.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/aries_cloudagent/anoncreds/default/legacy_indy/registry.py b/aries_cloudagent/anoncreds/default/legacy_indy/registry.py index e2c730d736..e4e63c05c9 100644 --- a/aries_cloudagent/anoncreds/default/legacy_indy/registry.py +++ b/aries_cloudagent/anoncreds/default/legacy_indy/registry.py @@ -383,8 +383,6 @@ async def register_revocation_registry_definition( rev_reg_def_id = self.make_rev_reg_def_id(revocation_registry_definition) try: - self._check_url(revocation_registry_definition.value.tails_location) - # Translate anoncreds object to indy object indy_rev_reg_def = { "ver": "1.0", From 4c1f36b24b9e99e9ae17f329b27aabea3b611e0a Mon Sep 17 00:00:00 2001 From: Char Howland Date: Fri, 5 May 2023 14:43:48 -0600 Subject: [PATCH 127/150] fix: pass issuer_id into init_issuer_registry() Signed-off-by: Char Howland --- aries_cloudagent/revocation/anoncreds.py | 2 ++ aries_cloudagent/revocation/routes.py | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/aries_cloudagent/revocation/anoncreds.py b/aries_cloudagent/revocation/anoncreds.py index bcf4612d98..11d4169832 100644 --- a/aries_cloudagent/revocation/anoncreds.py +++ b/aries_cloudagent/revocation/anoncreds.py @@ -166,7 +166,9 @@ async def get_or_create_active_registry( session, cred_def_id, {"$neq": IssuerRevRegRecord.STATE_FULL} ) if not rev_reg_recs: + issuer_id = cred_def_id.split(":")[0] await self.init_issuer_registry( + issuer_id, cred_def_id, max_cred_num=max_cred_num, ) diff --git a/aries_cloudagent/revocation/routes.py b/aries_cloudagent/revocation/routes.py index c16b4279de..4155919638 100644 --- a/aries_cloudagent/revocation/routes.py +++ b/aries_cloudagent/revocation/routes.py @@ -568,8 +568,9 @@ async def create_rev_reg(request: web.BaseRequest): try: revoc = AnonCredsRevocation(profile) + issuer_id = credential_definition_id.split(":")[0] issuer_rev_reg_rec = await revoc.init_issuer_registry( - # TODO: pass in issuer_id + issuer_id, credential_definition_id, max_cred_num=max_cred_num, notify=False, From 44ed2c3ac1cce553b8b83b1f21f2d6ded356b7f9 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Fri, 5 May 2023 14:44:27 -0600 Subject: [PATCH 128/150] fix: update MasterSecret references Signed-off-by: Char Howland --- aries_cloudagent/anoncreds/holder.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/aries_cloudagent/anoncreds/holder.py b/aries_cloudagent/anoncreds/holder.py index 9b9e59dfd2..cde0f83c56 100644 --- a/aries_cloudagent/anoncreds/holder.py +++ b/aries_cloudagent/anoncreds/holder.py @@ -12,10 +12,11 @@ Credential, CredentialRequest, CredentialRevocationState, - MasterSecret, Presentation, PresentCredentials, ) +from anoncreds.bindings import create_link_secret + from aries_askar import AskarError, AskarErrorCode from ..askar.profile import AskarProfile @@ -71,7 +72,7 @@ def profile(self): """Accessor for the profile instance.""" return self._profile - async def get_master_secret(self) -> MasterSecret: + async def get_master_secret(self) -> str: """Get or create the default master secret.""" while True: @@ -84,7 +85,7 @@ async def get_master_secret(self) -> MasterSecret: raise AnonCredsHolderError("Error fetching master secret") from err if record: try: - secret = MasterSecret.load(record.raw_value) + secret = record.raw_value except AnoncredsError as err: raise AnonCredsHolderError( "Error loading master secret" @@ -92,7 +93,7 @@ async def get_master_secret(self) -> MasterSecret: break else: try: - secret = MasterSecret.create() + secret = create_link_secret() except AnoncredsError as err: raise AnonCredsHolderError( "Error creating master secret" @@ -101,7 +102,7 @@ async def get_master_secret(self) -> MasterSecret: await session.handle.insert( CATEGORY_MASTER_SECRET, AnonCredsHolder.MASTER_SECRET_ID, - secret.to_json_buffer(), + secret, ) except AskarError as err: if err.code != AskarErrorCode.DUPLICATE: From c45170bb0f3b576c64937fddc665aa90c5d87ed5 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Fri, 5 May 2023 14:44:52 -0600 Subject: [PATCH 129/150] fix: remove RevocationRegistryDelta references Signed-off-by: Char Howland --- aries_cloudagent/anoncreds/issuer.py | 30 ---------------------------- 1 file changed, 30 deletions(-) diff --git a/aries_cloudagent/anoncreds/issuer.py b/aries_cloudagent/anoncreds/issuer.py index 232bca8e94..4b6c345ef4 100644 --- a/aries_cloudagent/anoncreds/issuer.py +++ b/aries_cloudagent/anoncreds/issuer.py @@ -15,7 +15,6 @@ CredentialOffer, CredentialRevocationConfig, RevocationRegistryDefinition, - RevocationRegistryDelta, RevocationStatusList, Schema, ) @@ -1023,32 +1022,3 @@ async def revoke_credentials( curr=updated_list, failed=[str(rev_id) for rev_id in sorted(failed_crids)], ) - - async def merge_revocation_registry_deltas( - self, fro_delta: str, to_delta: str - ) -> str: - """ - Merge revocation registry deltas. - - Args: - fro_delta: original delta in JSON format - to_delta: incoming delta in JSON format - - Returns: - Merged delta in JSON format - - """ - - def update(d1, d2): - try: - delta = RevocationRegistryDelta.load(d1) - delta.update_with(d2) - return delta.to_json() - except AnoncredsError as err: - raise AnonCredsIssuerError( - "Error merging revocation registry deltas" - ) from err - - return await asyncio.get_event_loop().run_in_executor( - None, update, fro_delta, to_delta - ) From 0eb5ac439bc20fa9af6c8b7ad8087d54b3017379 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Fri, 5 May 2023 17:34:52 -0400 Subject: [PATCH 130/150] fix: object ids not necessarily known until finished Signed-off-by: Daniel Bluhm --- aries_cloudagent/anoncreds/base.py | 28 +-- .../anoncreds/default/legacy_indy/registry.py | 4 +- aries_cloudagent/anoncreds/issuer.py | 177 ++++++++++++------ .../anoncreds/models/anoncreds_cred_def.py | 9 +- aries_cloudagent/anoncreds/routes.py | 4 - aries_cloudagent/ledger/base.py | 5 +- aries_cloudagent/ledger/error.py | 20 +- 7 files changed, 156 insertions(+), 91 deletions(-) diff --git a/aries_cloudagent/anoncreds/base.py b/aries_cloudagent/anoncreds/base.py index 071204ebd2..24fef9c45e 100644 --- a/aries_cloudagent/anoncreds/base.py +++ b/aries_cloudagent/anoncreds/base.py @@ -1,6 +1,6 @@ """Base Registry.""" from abc import ABC, abstractmethod -from typing import Generic, Optional, Pattern, Tuple, TypeVar +from typing import Generic, Optional, Pattern, TypeVar from ..config.injection_context import InjectionContext from ..core.error import BaseError @@ -47,35 +47,35 @@ class AnonCredsObjectAlreadyExists(AnonCredsRegistrationError, Generic[T]): """Raised when an AnonCreds object already exists.""" def __init__( - self, message: Optional[str] = None, obj: Optional[T] = None, *args, **kwargs + self, + message: str, + obj_id: str, + obj: T = None, + *args, + **kwargs, ): - """Constructor.""" - super().__init__(message, obj, *args, **kwargs) + super().__init__(message, obj_id, obj, *args, **kwargs) + self._message = message + self.obj_id = obj_id self.obj = obj @property def message(self): - """Message property.""" - if self.args[0] and self.args[1]: - return f"{self.args[0]}: {self.args[1]}" - else: - return super().message + return f"{self._message}: {self.obj_id}, {self.obj}" -class AnonCredsSchemaAlreadyExists( - AnonCredsObjectAlreadyExists[Tuple[str, AnonCredsSchema]] -): +class AnonCredsSchemaAlreadyExists(AnonCredsObjectAlreadyExists[AnonCredsSchema]): """Raised when a schema already exists.""" @property def schema_id(self): """Get Schema Id.""" - return self.obj[0] if self.obj else None + return self.obj_id @property def schema(self): """Get Schema.""" - return self.obj[1] if self.obj else None + return self.obj class AnonCredsResolutionError(BaseAnonCredsError): diff --git a/aries_cloudagent/anoncreds/default/legacy_indy/registry.py b/aries_cloudagent/anoncreds/default/legacy_indy/registry.py index e4e63c05c9..4385a15095 100644 --- a/aries_cloudagent/anoncreds/default/legacy_indy/registry.py +++ b/aries_cloudagent/anoncreds/default/legacy_indy/registry.py @@ -207,14 +207,14 @@ async def register_schema( try: seq_no = await shield(ledger.send_schema(schema_id, indy_schema)) except LedgerObjectAlreadyExistsError as err: - indy_schema = err.obj[1] + indy_schema = err.obj schema = AnonCredsSchema( name=indy_schema["name"], version=indy_schema["version"], attr_names=indy_schema["attrNames"], issuer_id=indy_schema["id"].split(":")[0], ) - raise AnonCredsSchemaAlreadyExists(err.message, (err.obj[0], schema)) + raise AnonCredsSchemaAlreadyExists(err.message, err.obj_id, schema) except (AnonCredsIssuerError, LedgerError) as err: raise AnonCredsRegistrationError("Failed to register schema") from err diff --git a/aries_cloudagent/anoncreds/issuer.py b/aries_cloudagent/anoncreds/issuer.py index 4b6c345ef4..4fad8ddf09 100644 --- a/aries_cloudagent/anoncreds/issuer.py +++ b/aries_cloudagent/anoncreds/issuer.py @@ -20,11 +20,11 @@ ) from aries_askar import AskarError -from ..askar.profile import AskarProfile +from ..askar.profile import AskarProfile, AskarProfileSession from ..core.error import BaseError from ..tails.base import BaseTailsServer from .base import AnonCredsRegistrationError, AnonCredsSchemaAlreadyExists -from .models.anoncreds_cred_def import CredDef, CredDefResult, CredDefState +from .models.anoncreds_cred_def import CredDef, CredDefResult from .models.anoncreds_revocation import ( RevList, RevRegDef, @@ -48,6 +48,14 @@ CATEGORY_REV_REG_DEF = "revocation_reg_def" CATEGORY_REV_REG_DEF_PRIVATE = "revocation_reg_def_private" CATEGORY_REV_REG_ISSUER = "revocation_reg_def_issuer" +STATE_FINISHED = "finished" + +EVENT_PREFIX = "acapy::anoncreds::" +EVENT_SCHEMA = EVENT_PREFIX + CATEGORY_SCHEMA +EVENT_CRED_DEF = EVENT_PREFIX + CATEGORY_CRED_DEF +EVENT_REV_REG_DEF = EVENT_PREFIX + CATEGORY_REV_REG_DEF +EVENT_REV_LIST = EVENT_PREFIX + CATEGORY_REV_LIST +EVENT_FINISHED_SUFFIX = "::" + STATE_FINISHED class AnonCredsIssuerError(BaseError): @@ -65,11 +73,39 @@ class RevokeResult(NamedTuple): class AnonCredsIssuer: - """AnonCreds issuer class.""" + """AnonCreds issuer class. + + This class provides methods for creating and registering AnonCreds objects + needed to issue credentials. It also provides methods for storing and + retrieving local representations of these objects from the wallet. + + A general pattern is followed when creating and registering objects: + + 1. Create the object locally + 2. Register the object with the anoncreds registry + 3. Store the object in the wallet + + The wallet storage is used to keep track of the state of the object. + + If the object is fully registered immediately after sending to the registry + (state of `finished`), the object is saved to the wallet with an id + matching the id returned from the registry. + + If the object is not fully registered but pending (state of `wait`), the + object is saved to the wallet with an id matching the job id returned from + the registry. + + If the object fails to register (state of `failed`), the object is saved to + the wallet with an id matching the job id returned from the registry. + + When an object finishes registration after being in a pending state (moving + from state `wait` to state `finished`), the wallet entry matching the job id + is removed and an entry matching the registered id is added. + """ def __init__(self, profile: AskarProfile): """ - Initialize an IndyCredxIssuer instance. + Initialize an AnonCredsIssuer instance. Args: profile: The active profile instance @@ -105,24 +141,49 @@ async def _update_entry_state(self, category: str, name: str, state: str): except AskarError as err: raise AnonCredsIssuerError(f"Error marking {category} as {state}") from err + async def _finish_registration( + self, txn: AskarProfileSession, category: str, job_id: str, registered_id: str + ): + entry = await txn.handle.fetch( + category, + job_id, + for_update=True, + ) + if not entry: + raise AnonCredsIssuerError( + f"{category} with job id {job_id} could not be found" + ) + + tags = entry.tags + tags["state"] = STATE_FINISHED + await txn.handle.insert( + category, + registered_id, + value=entry.value, + tags=tags, + ) + await txn.handle.remove(category, job_id) + async def _store_schema( self, - record_id: str, - schema: AnonCredsSchema, - state: str, + result: SchemaResult, ): """Store schema after reaching finished state.""" + ident = result.schema_state.schema_id or result.job_id + if not ident: + raise ValueError("Schema id or job id must be set") + try: async with self._profile.session() as session: await session.handle.insert( CATEGORY_SCHEMA, - record_id, - schema.to_json(), + ident, + result.schema_state.schema.to_json(), { - "name": schema.name, - "version": schema.version, - "issuer_id": schema.issuer_id, - "state": state, + "name": result.schema_state.schema.name, + "version": result.schema_state.schema.version, + "issuer_id": result.schema_state.schema.issuer_id, + "state": result.schema_state.state, }, ) except AskarError as err: @@ -163,25 +224,21 @@ async def create_and_register_schema( ) if schemas: raise AnonCredsSchemaAlreadyExists( - f"Schema with {name}: {version} " f"already exists for {issuer_id}" + f"Schema with {name}: {version} " f"already exists for {issuer_id}", + schemas[0].name, + AnonCredsSchema.deserialize(schemas[0].value_json), ) - # TODO Do we even need to create the native object here? schema = Schema.create(name, version, issuer_id, attr_names) try: anoncreds_registry = self._profile.inject(AnonCredsRegistry) - schema_result = await anoncreds_registry.register_schema( self.profile, AnonCredsSchema.from_native(schema), options, ) - await self._store_schema( - schema_result.schema_state.schema_id or schema_result.job_id, - schema_result.schema_state.schema, - state=schema_result.schema_state.state, - ) + await self._store_schema(schema_result) return schema_result @@ -189,31 +246,33 @@ async def create_and_register_schema( # If we find that we've previously written a schema that looks like # this one before but that schema is not in our wallet, add it to # the wallet so we can return from our get schema calls - if err.schema_id and err.schema: - await self._store_schema( - err.schema_id, err.schema, SchemaState.STATE_FINISHED + await self._store_schema( + SchemaResult( + job_id=None, + schema_state=SchemaState( + state=SchemaState.STATE_FINISHED, + schema_id=err.schema_id, + schema=err.schema, + ), ) - raise AnonCredsIssuerError( - "Schema already exists but was not in wallet; stored in wallet" - ) from err - raise + ) + raise AnonCredsIssuerError( + "Schema already exists but was not in wallet; stored in wallet" + ) from err except AnoncredsError as err: raise AnonCredsIssuerError("Error creating schema") from err - async def update_schema_state(self, schema_id: str, state: str): - """Update the state of the stored schema.""" - await self._update_entry_state(CATEGORY_SCHEMA, schema_id, state) - - async def finish_schema(self, schema_id: str): + async def finish_schema(self, job_id: str, schema_id: str): """Mark a schema as finished.""" - await self.update_schema_state(schema_id, SchemaState.STATE_FINISHED) + async with self.profile.transaction() as txn: + await self._finish_registration(txn, CATEGORY_SCHEMA, job_id, schema_id) + await txn.commit() async def get_created_schemas( self, name: Optional[str] = None, version: Optional[str] = None, issuer_id: Optional[str] = None, - state: Optional[str] = None, ) -> Sequence[str]: """Retrieve IDs of schemas previously created.""" async with self._profile.session() as session: @@ -226,7 +285,7 @@ async def get_created_schemas( "name": name, "version": version, "issuer_id": issuer_id, - "state": state, + "state": STATE_FINISHED, }.items() if value is not None }, @@ -234,15 +293,6 @@ async def get_created_schemas( # entry.name was stored as the schema's ID return [entry.name for entry in schemas] - @staticmethod - def make_credential_definition_id( - origin_did: str, schema: dict, signature_type: str = None, tag: str = None - ) -> str: - """Derive the ID for a credential definition.""" - signature_type = signature_type or DEFAULT_SIGNATURE_TYPE - tag = tag or DEFAULT_CRED_DEF_TAG - return f"{origin_did}:3:{signature_type}:{str(schema['seqNo'])}:{tag}" - async def credential_definition_in_wallet( self, credential_definition_id: str ) -> bool: @@ -322,14 +372,18 @@ async def create_and_register_credential_definition( raise AnonCredsIssuerError("Error creating credential definition") from err # Store the cred def and it's components + ident = ( + result.credential_definition_state.credential_definition_id or result.job_id + ) + if not ident: + raise AnonCredsIssuerError("cred def id or job id required") + try: - cred_def_id = result.credential_definition_state.credential_definition_id async with self._profile.transaction() as txn: await txn.handle.insert( CATEGORY_CRED_DEF, - cred_def_id, + ident, cred_def_json, - # Note: Indy-SDK uses a separate SchemaId record for this tags={ "schema_id": schema_id, "schema_issuer_id": schema_result.schema.issuer_id, @@ -342,11 +396,11 @@ async def create_and_register_credential_definition( ) await txn.handle.insert( CATEGORY_CRED_DEF_PRIVATE, - cred_def_id, + ident, cred_def_private.to_json_buffer(), ) await txn.handle.insert( - CATEGORY_CRED_DEF_KEY_PROOF, cred_def_id, key_proof.to_json_buffer() + CATEGORY_CRED_DEF_KEY_PROOF, ident, key_proof.to_json_buffer() ) await txn.commit() except AskarError as err: @@ -354,13 +408,17 @@ async def create_and_register_credential_definition( return result - async def update_cred_def_state(self, cred_def_id: str, state: str): - """Update the state of a cred def.""" - await self._update_entry_state(CATEGORY_CRED_DEF, cred_def_id, state) - - async def finish_cred_def(self, cred_def_id: str): + async def finish_cred_def(self, job_id: str, cred_def_id: str): """Finish a cred def.""" - await self.update_cred_def_state(cred_def_id, CredDefState.STATE_FINISHED) + async with self.profile.transaction() as txn: + await self._finish_registration(txn, CATEGORY_CRED_DEF, job_id, cred_def_id) + await self._finish_registration( + txn, CATEGORY_CRED_DEF_PRIVATE, job_id, cred_def_id + ) + await self._finish_registration( + txn, CATEGORY_CRED_DEF_KEY_PROOF, job_id, cred_def_id + ) + await txn.commit() async def get_created_credential_definitions( self, @@ -369,7 +427,6 @@ async def get_created_credential_definitions( schema_id: Optional[str] = None, schema_name: Optional[str] = None, schema_version: Optional[str] = None, - state: Optional[str] = None, epoch: Optional[str] = None, ) -> Sequence[str]: """Retrieve IDs of credential definitions previously created.""" @@ -385,12 +442,13 @@ async def get_created_credential_definitions( "schema_id": schema_id, "schema_name": schema_name, "schema_version": schema_version, - "state": state, "epoch": epoch, + "state": STATE_FINISHED, }.items() if value is not None }, ) + # entry.name is cred def id when state == finished return [entry.name for entry in credential_definition_entries] async def match_created_credential_definitions( @@ -401,7 +459,6 @@ async def match_created_credential_definitions( schema_id: Optional[str] = None, schema_name: Optional[str] = None, schema_version: Optional[str] = None, - state: Optional[str] = None, epoch: Optional[str] = None, ) -> Optional[str]: """Return cred def id of most recent matching cred def.""" @@ -422,7 +479,7 @@ async def match_created_credential_definitions( "schema_id": schema_id, "schema_name": schema_name, "schema_version": schema_version, - "state": state, + "state": STATE_FINISHED, "epoch": epoch, }.items() if value is not None diff --git a/aries_cloudagent/anoncreds/models/anoncreds_cred_def.py b/aries_cloudagent/anoncreds/models/anoncreds_cred_def.py index 7385b6d6f7..1a5c729225 100644 --- a/aries_cloudagent/anoncreds/models/anoncreds_cred_def.py +++ b/aries_cloudagent/anoncreds/models/anoncreds_cred_def.py @@ -207,7 +207,10 @@ class Meta: schema_class = "CredDefStateSchema" def __init__( - self, state: str, credential_definition_id: str, credential_definition: CredDef + self, + state: str, + credential_definition_id: Optional[str], + credential_definition: CredDef, ): self.state = state self.credential_definition_id = credential_definition_id @@ -233,7 +236,9 @@ class Meta: ] ) ) - credential_definition_id = fields.Str(description="credential definition id") + credential_definition_id = fields.Str( + description="credential definition id", allow_none=True + ) credential_definition = fields.Nested( CredDefSchema(), description="credential definition" ) diff --git a/aries_cloudagent/anoncreds/routes.py b/aries_cloudagent/anoncreds/routes.py index e417b9e29f..2683ae979e 100644 --- a/aries_cloudagent/anoncreds/routes.py +++ b/aries_cloudagent/anoncreds/routes.py @@ -86,9 +86,6 @@ class CredDefsQueryStringSchema(OpenAPISchema): description="Schema name", ) schema_version = fields.Str(description="Schema version") - state = fields.Str( - description="Credential definition state", - ) class SchemaPostOptionSchema(OpenAPISchema): @@ -312,7 +309,6 @@ async def cred_defs_get(request: web.BaseRequest): schema_id=request.query.get("schema_id"), schema_name=request.query.get("schema_name"), schema_version=request.query.get("schema_version"), - state=request.query.get("state"), ) return web.json_response(cred_def_ids) diff --git a/aries_cloudagent/ledger/base.py b/aries_cloudagent/ledger/base.py index 57d301c976..5252082e94 100644 --- a/aries_cloudagent/ledger/base.py +++ b/aries_cloudagent/ledger/base.py @@ -287,7 +287,7 @@ async def send_schema( if schema_info: raise LedgerObjectAlreadyExistsError( - "Schema already exists on ledger", schema_info + "Schema already exists on ledger", *schema_info ) if await self.is_ledger_read_only(): @@ -340,7 +340,7 @@ async def send_schema( e, ) raise LedgerObjectAlreadyExistsError( - f"Schema already exists on ledger (Error: {e})", schema_info + f"Schema already exists on ledger (Error: {e})", *schema_info ) else: raise @@ -422,6 +422,7 @@ async def send_credential_definition( raise LedgerObjectAlreadyExistsError( f"Credential definition with id {cred_def_id} " "already exists in wallet and on ledger.", + cred_def_id, credential_definition_json, ) diff --git a/aries_cloudagent/ledger/error.py b/aries_cloudagent/ledger/error.py index 49b87b2b16..b7dd025236 100644 --- a/aries_cloudagent/ledger/error.py +++ b/aries_cloudagent/ledger/error.py @@ -1,6 +1,6 @@ """Ledger related errors.""" -from typing import Any, Generic, Optional, TypeVar +from typing import Generic, TypeVar from ..core.error import BaseError @@ -30,13 +30,19 @@ class LedgerTransactionError(LedgerError): class LedgerObjectAlreadyExistsError(LedgerError, Generic[T]): """Raised when a ledger object already existed.""" - def __init__(self, message: Optional[str] = None, obj: T = None, *args, **kwargs): - super().__init__(message, obj, *args, **kwargs) + def __init__( + self, + message: str, + obj_id: str, + obj: T = None, + *args, + **kwargs, + ): + super().__init__(message, obj_id, obj, *args, **kwargs) + self._message = message + self.obj_id = obj_id self.obj = obj @property def message(self): - if self.args[0] and self.args[1]: - return f"{self.args[0]}: {self.args[1]}" - else: - return super().message + return f"{self._message}: {self.obj_id}, {self.obj}" From 57d1e4bd394ff97f495ae6058b809cc78769d91f Mon Sep 17 00:00:00 2001 From: Adam Burdett Date: Fri, 19 May 2023 15:37:40 -0600 Subject: [PATCH 131/150] feat(tailsfile): generic tails file upload Signed-off-by: Adam Burdett --- aries_cloudagent/anoncreds/issuer.py | 2 ++ aries_cloudagent/anoncreds/routes.py | 50 ++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/aries_cloudagent/anoncreds/issuer.py b/aries_cloudagent/anoncreds/issuer.py index 4fad8ddf09..f112ff6c15 100644 --- a/aries_cloudagent/anoncreds/issuer.py +++ b/aries_cloudagent/anoncreds/issuer.py @@ -646,6 +646,8 @@ async def upload_tails_file(self, rev_reg_def: RevRegDef): f"Tails file for rev reg for {rev_reg_def.cred_def_id} " "uploaded to wrong location: {result}" ) + # TODO: do we need to set uri? something like.. + # await self.set_tails_file_public_uri(profile, result) async def update_revocation_registry_definition_state( self, rev_reg_def_id: str, state: str diff --git a/aries_cloudagent/anoncreds/routes.py b/aries_cloudagent/anoncreds/routes.py index 2683ae979e..fd0491cefe 100644 --- a/aries_cloudagent/anoncreds/routes.py +++ b/aries_cloudagent/anoncreds/routes.py @@ -11,6 +11,12 @@ response_schema, ) from marshmallow import fields +from aries_cloudagent.askar.profile import AskarProfile + +from aries_cloudagent.revocation.routes import ( + RevRegIdMatchInfoSchema, + RevocationModuleResponseSchema, +) from ..admin.request_context import AdminRequestContext from ..messaging.models.openapi import OpenAPISchema @@ -21,6 +27,8 @@ from .issuer import AnonCredsIssuer, AnonCredsIssuerError from .models.anoncreds_cred_def import CredDefResultSchema, GetCredDefResultSchema from .models.anoncreds_revocation import ( + AnonCredsRegistryGetRevocationRegistryDefinition, + RevRegDef, RevRegDefResultSchema, RevListResultSchema, ) @@ -439,6 +447,47 @@ async def rev_list_post(request: web.BaseRequest): return web.json_response(result.serialize()) +@docs( + tags=["revocation"], + summary="Upload local tails file to server", +) +@match_info_schema(RevRegIdMatchInfoSchema()) +@response_schema(RevocationModuleResponseSchema(), description="") +async def upload_tails_file(request: web.BaseRequest): + """ + Request handler to upload local tails file for revocation registry. + + Args: + request: aiohttp request object + + """ + context: AdminRequestContext = request["context"] + profile: AskarProfile = context.profile + anoncreds_registry: AnonCredsRegistry = context.inject(AnonCredsRegistry) + rev_reg_id = request.match_info["rev_reg_id"] + try: + issuer = AnonCredsIssuer(profile) + get_rev_reg_def: AnonCredsRegistryGetRevocationRegistryDefinition = ( + await anoncreds_registry.get_revocation_registry_definition( + profile, rev_reg_id + ) + ) + rev_reg_def: RevRegDef = get_rev_reg_def.revocation_registry + # TODO: Should we check if tails file exists + except StorageNotFoundError as err: # TODO: update error + raise web.HTTPNotFound(reason=err.roll_up) from err + try: + await issuer.upload_tails_file( + rev_reg_def.value.tails_hash, + rev_reg_def.cred_def_id, + rev_reg_def.value.tails_location, + ) + except AnonCredsIssuerError as e: + raise web.HTTPInternalServerError(reason=str(e)) from e + + return web.json_response({}) + + async def register(app: web.Application): """Register routes.""" @@ -460,6 +509,7 @@ async def register(app: web.Application): ), web.post("/anoncreds/revocation-registry-definition", rev_reg_def_post), web.post("/anoncreds/revocation-list", rev_list_post), + web.put("/revocation/registry/{rev_reg_id}/tails-file", upload_tails_file), ] ) From d5135c4ac096d782b18097e8196745899701d8f7 Mon Sep 17 00:00:00 2001 From: Adam Burdett Date: Fri, 19 May 2023 15:40:05 -0600 Subject: [PATCH 132/150] fix(tailsfile): generic tails file upload Signed-off-by: Adam Burdett --- aries_cloudagent/anoncreds/routes.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/aries_cloudagent/anoncreds/routes.py b/aries_cloudagent/anoncreds/routes.py index fd0491cefe..7d81e229a1 100644 --- a/aries_cloudagent/anoncreds/routes.py +++ b/aries_cloudagent/anoncreds/routes.py @@ -477,11 +477,7 @@ async def upload_tails_file(request: web.BaseRequest): except StorageNotFoundError as err: # TODO: update error raise web.HTTPNotFound(reason=err.roll_up) from err try: - await issuer.upload_tails_file( - rev_reg_def.value.tails_hash, - rev_reg_def.cred_def_id, - rev_reg_def.value.tails_location, - ) + await issuer.upload_tails_file(rev_reg_def) except AnonCredsIssuerError as e: raise web.HTTPInternalServerError(reason=str(e)) from e From 674c8daeae93dc0fafb1296e179c3f89e18646c5 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Mon, 22 May 2023 13:41:42 -0400 Subject: [PATCH 133/150] refactor: shorten get rev reg def result object name Signed-off-by: Daniel Bluhm --- aries_cloudagent/anoncreds/base.py | 4 ++-- .../anoncreds/default/did_indy/registry.py | 4 ++-- .../anoncreds/default/did_web/registry.py | 19 ++++--------------- .../anoncreds/default/legacy_indy/registry.py | 4 ++-- .../anoncreds/models/anoncreds_revocation.py | 14 +++++++------- aries_cloudagent/anoncreds/registry.py | 4 ++-- aries_cloudagent/anoncreds/routes.py | 6 +++--- 7 files changed, 22 insertions(+), 33 deletions(-) diff --git a/aries_cloudagent/anoncreds/base.py b/aries_cloudagent/anoncreds/base.py index 24fef9c45e..c2d52cd3bd 100644 --- a/aries_cloudagent/anoncreds/base.py +++ b/aries_cloudagent/anoncreds/base.py @@ -12,7 +12,7 @@ ) from .models.anoncreds_revocation import ( GetRevListResult, - AnonCredsRegistryGetRevocationRegistryDefinition, + GetRevRegDefResult, RevRegDef, RevRegDefResult, RevList, @@ -115,7 +115,7 @@ async def get_credential_definition( @abstractmethod async def get_revocation_registry_definition( self, profile: Profile, revocation_registry_id: str - ) -> AnonCredsRegistryGetRevocationRegistryDefinition: + ) -> GetRevRegDefResult: """Get a revocation registry definition from the registry.""" @abstractmethod diff --git a/aries_cloudagent/anoncreds/default/did_indy/registry.py b/aries_cloudagent/anoncreds/default/did_indy/registry.py index a262e00c74..f022b70d41 100644 --- a/aries_cloudagent/anoncreds/default/did_indy/registry.py +++ b/aries_cloudagent/anoncreds/default/did_indy/registry.py @@ -12,7 +12,7 @@ ) from ...models.anoncreds_revocation import ( GetRevListResult, - AnonCredsRegistryGetRevocationRegistryDefinition, + GetRevRegDefResult, RevRegDef, RevRegDefResult, RevList, @@ -70,7 +70,7 @@ async def register_credential_definition( async def get_revocation_registry_definition( self, profile: Profile, revocation_registry_id: str - ) -> AnonCredsRegistryGetRevocationRegistryDefinition: + ) -> GetRevRegDefResult: """Get a revocation registry definition from the registry.""" raise NotImplementedError() diff --git a/aries_cloudagent/anoncreds/default/did_web/registry.py b/aries_cloudagent/anoncreds/default/did_web/registry.py index dea890858b..197a0d2a1b 100644 --- a/aries_cloudagent/anoncreds/default/did_web/registry.py +++ b/aries_cloudagent/anoncreds/default/did_web/registry.py @@ -4,26 +4,15 @@ from ....config.injection_context import InjectionContext from ....core.profile import Profile -from ...models.anoncreds_cred_def import ( - GetCredDefResult, -) -from ...models.anoncreds_revocation import ( - GetRevListResult, - AnonCredsRegistryGetRevocationRegistryDefinition, - RevRegDef, - RevList, - RevListResult, -) -from ...models.anoncreds_schema import GetSchemaResult from ...base import BaseAnonCredsRegistrar, BaseAnonCredsResolver from ...models.anoncreds_cred_def import CredDef, CredDefResult, GetCredDefResult from ...models.anoncreds_revocation import ( - AnonCredsRegistryGetRevocationRegistryDefinition, GetRevListResult, - RevRegDef, - RevRegDefResult, + GetRevRegDefResult, RevList, RevListResult, + RevRegDef, + RevRegDefResult, ) from ...models.anoncreds_schema import AnonCredsSchema, GetSchemaResult, SchemaResult @@ -76,7 +65,7 @@ async def register_credential_definition( async def get_revocation_registry_definition( self, profile: Profile, revocation_registry_id: str - ) -> AnonCredsRegistryGetRevocationRegistryDefinition: + ) -> GetRevRegDefResult: """Get a revocation registry definition from the registry.""" raise NotImplementedError() diff --git a/aries_cloudagent/anoncreds/default/legacy_indy/registry.py b/aries_cloudagent/anoncreds/default/legacy_indy/registry.py index 4385a15095..e95dabf4d0 100644 --- a/aries_cloudagent/anoncreds/default/legacy_indy/registry.py +++ b/aries_cloudagent/anoncreds/default/legacy_indy/registry.py @@ -41,7 +41,7 @@ GetCredDefResult, ) from ...models.anoncreds_revocation import ( - AnonCredsRegistryGetRevocationRegistryDefinition, + GetRevRegDefResult, GetRevListResult, RevRegDef, RevRegDefResult, @@ -357,7 +357,7 @@ async def register_credential_definition( async def get_revocation_registry_definition( self, profile: Profile, rev_reg_def_id: str - ) -> AnonCredsRegistryGetRevocationRegistryDefinition: + ) -> GetRevRegDefResult: """Get a revocation registry definition from the registry.""" try: diff --git a/aries_cloudagent/anoncreds/models/anoncreds_revocation.py b/aries_cloudagent/anoncreds/models/anoncreds_revocation.py index 002daa997a..0a88c17740 100644 --- a/aries_cloudagent/anoncreds/models/anoncreds_revocation.py +++ b/aries_cloudagent/anoncreds/models/anoncreds_revocation.py @@ -205,13 +205,13 @@ class Meta: revocation_registry_definition_metadata = fields.Dict() -class AnonCredsRegistryGetRevocationRegistryDefinition(BaseModel): - """AnonCredsRegistryGetRevocationRegistryDefinition""" +class GetRevRegDefResult(BaseModel): + """GetRevRegDefResult""" class Meta: - """AnonCredsRegistryGetRevocationRegistryDefinition metadata.""" + """GetRevRegDefResult metadata.""" - schema_class = "AnonCredsRegistryGetRevocationRegistryDefinitionSchema" + schema_class = "GetRevRegDefResultSchema" def __init__( self, @@ -228,11 +228,11 @@ def __init__( self.revocation_registry_metadata = revocation_registry_metadata -class AnonCredsRegistryGetRevocationRegistryDefinitionSchema(BaseModelSchema): +class GetRevRegDefResultSchema(BaseModelSchema): class Meta: - """AnonCredsRegistryGetRevocationRegistryDefinitionSchema metadata.""" + """GetRevRegDefResultSchema metadata.""" - model_class = AnonCredsRegistryGetRevocationRegistryDefinition + model_class = GetRevRegDefResult unknown = EXCLUDE revocation_registry = fields.Nested(RevRegDefSchema()) diff --git a/aries_cloudagent/anoncreds/registry.py b/aries_cloudagent/anoncreds/registry.py index c3b9b9b6bb..1412471ee3 100644 --- a/aries_cloudagent/anoncreds/registry.py +++ b/aries_cloudagent/anoncreds/registry.py @@ -11,7 +11,7 @@ ) from .models.anoncreds_revocation import ( GetRevListResult, - AnonCredsRegistryGetRevocationRegistryDefinition, + GetRevRegDefResult, RevRegDef, RevRegDefResult, RevList, @@ -119,7 +119,7 @@ async def register_credential_definition( async def get_revocation_registry_definition( self, profile: Profile, revocation_registry_id: str - ) -> AnonCredsRegistryGetRevocationRegistryDefinition: + ) -> GetRevRegDefResult: """Get a revocation registry definition from the registry.""" resolver = await self._resolver_for_identifier(revocation_registry_id) return await resolver.get_revocation_registry_definition( diff --git a/aries_cloudagent/anoncreds/routes.py b/aries_cloudagent/anoncreds/routes.py index 7d81e229a1..8eeccd03ae 100644 --- a/aries_cloudagent/anoncreds/routes.py +++ b/aries_cloudagent/anoncreds/routes.py @@ -27,7 +27,7 @@ from .issuer import AnonCredsIssuer, AnonCredsIssuerError from .models.anoncreds_cred_def import CredDefResultSchema, GetCredDefResultSchema from .models.anoncreds_revocation import ( - AnonCredsRegistryGetRevocationRegistryDefinition, + GetRevRegDefResult, RevRegDef, RevRegDefResultSchema, RevListResultSchema, @@ -467,7 +467,7 @@ async def upload_tails_file(request: web.BaseRequest): rev_reg_id = request.match_info["rev_reg_id"] try: issuer = AnonCredsIssuer(profile) - get_rev_reg_def: AnonCredsRegistryGetRevocationRegistryDefinition = ( + get_rev_reg_def: GetRevRegDefResult = ( await anoncreds_registry.get_revocation_registry_definition( profile, rev_reg_id ) @@ -505,7 +505,7 @@ async def register(app: web.Application): ), web.post("/anoncreds/revocation-registry-definition", rev_reg_def_post), web.post("/anoncreds/revocation-list", rev_list_post), - web.put("/revocation/registry/{rev_reg_id}/tails-file", upload_tails_file), + web.put("/anoncreds/registry/{rev_reg_id}/tails-file", upload_tails_file), ] ) From 5ff529eaa80992c841c2bf61d0c58b5cec083254 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Mon, 22 May 2023 14:06:41 -0400 Subject: [PATCH 134/150] fix: get rev reg def in legacy indy registry Signed-off-by: Daniel Bluhm --- anoncreds_test.py | 6 +++ .../anoncreds/default/legacy_indy/registry.py | 51 +++++++++++++++---- aries_cloudagent/anoncreds/routes.py | 5 +- 3 files changed, 50 insertions(+), 12 deletions(-) diff --git a/anoncreds_test.py b/anoncreds_test.py index 7845159ded..59b7eecde8 100644 --- a/anoncreds_test.py +++ b/anoncreds_test.py @@ -64,6 +64,12 @@ async def main(): "maxCredNum": 10, }, ) + rev_reg_def_id = rev_reg_def["revocation_registry_definition_state"][ + "revocation_registry_definition_id" + ] + tails = await alice.put( + f"/anoncreds/registry/{rev_reg_def_id}/tails-file", + ) rev_status_list = await alice.post( "/anoncreds/revocation-list", json={ diff --git a/aries_cloudagent/anoncreds/default/legacy_indy/registry.py b/aries_cloudagent/anoncreds/default/legacy_indy/registry.py index e95dabf4d0..6063c8152c 100644 --- a/aries_cloudagent/anoncreds/default/legacy_indy/registry.py +++ b/aries_cloudagent/anoncreds/default/legacy_indy/registry.py @@ -19,10 +19,8 @@ IndyLedgerRequestsExecutor, ) from ....multitenant.base import BaseMultitenantManager -from ....revocation.anoncreds import AnonCredsRevocation from ....revocation.models.issuer_cred_rev_record import IssuerCredRevRecord from ....revocation.recover import generate_ledger_rrrecovery_txn -from ....storage.error import StorageNotFoundError from ...base import ( AnonCredsObjectAlreadyExists, AnonCredsObjectNotFound, @@ -49,6 +47,7 @@ RevList, RevListResult, RevListState, + RevRegDefValue, ) from ...models.anoncreds_schema import ( AnonCredsSchema, @@ -359,15 +358,49 @@ async def get_revocation_registry_definition( self, profile: Profile, rev_reg_def_id: str ) -> GetRevRegDefResult: """Get a revocation registry definition from the registry.""" + async with profile.session() as session: + multitenant_mgr = session.inject_or(BaseMultitenantManager) + if multitenant_mgr: + ledger_exec_inst = IndyLedgerRequestsExecutor(profile) + else: + ledger_exec_inst = session.inject(IndyLedgerRequestsExecutor) - try: - revoc = AnonCredsRevocation(profile) - rev_reg = await revoc.get_issuer_rev_reg_record(rev_reg_def_id) - except StorageNotFoundError as err: - raise AnonCredsResolutionError(err) + ledger_id, ledger = await ledger_exec_inst.get_ledger_for_identifier( + rev_reg_def_id, + txn_record_type=GET_CRED_DEF, + ) + if not ledger: + reason = "No ledger available" + if not profile.settings.get_value("wallet.type"): + reason += ": missing wallet-type?" + raise AnonCredsResolutionError(reason) + + async with ledger: + rev_reg_def = await ledger.get_revoc_reg_def(rev_reg_def_id) - return rev_reg.serialize - # use AnonCredsRevocationRegistryDefinition object + if rev_reg_def is None: + raise AnonCredsObjectNotFound( + f"Revocation registry definition not found: {rev_reg_def_id}", + {"ledger_id": ledger_id}, + ) + + LOGGER.debug("Retrieved revocation registry definition: %s", rev_reg_def) + rev_reg_def_value = RevRegDefValue.deserialize(rev_reg_def["value"]) + anoncreds_rev_reg_def = RevRegDef( + issuer_id=rev_reg_def["id"].split(":")[0], + cred_def_id=rev_reg_def["credDefId"], + type=rev_reg_def["revocDefType"], + value=rev_reg_def_value, + tag=rev_reg_def["tag"], + ) + result = GetRevRegDefResult( + revocation_registry=anoncreds_rev_reg_def, + revocation_registry_id=rev_reg_def["id"], + resolution_metadata={}, + revocation_registry_metadata={}, + ) + + return result async def get_revocation_registry_definitions(self, profile: Profile, filter: str): """Get credential definition ids filtered by filter""" diff --git a/aries_cloudagent/anoncreds/routes.py b/aries_cloudagent/anoncreds/routes.py index 8eeccd03ae..0ccfde3be2 100644 --- a/aries_cloudagent/anoncreds/routes.py +++ b/aries_cloudagent/anoncreds/routes.py @@ -27,7 +27,6 @@ from .issuer import AnonCredsIssuer, AnonCredsIssuerError from .models.anoncreds_cred_def import CredDefResultSchema, GetCredDefResultSchema from .models.anoncreds_revocation import ( - GetRevRegDefResult, RevRegDef, RevRegDefResultSchema, RevListResultSchema, @@ -467,12 +466,12 @@ async def upload_tails_file(request: web.BaseRequest): rev_reg_id = request.match_info["rev_reg_id"] try: issuer = AnonCredsIssuer(profile) - get_rev_reg_def: GetRevRegDefResult = ( + rev_reg_def_result = ( await anoncreds_registry.get_revocation_registry_definition( profile, rev_reg_id ) ) - rev_reg_def: RevRegDef = get_rev_reg_def.revocation_registry + rev_reg_def: RevRegDef = rev_reg_def_result.revocation_registry # TODO: Should we check if tails file exists except StorageNotFoundError as err: # TODO: update error raise web.HTTPNotFound(reason=err.roll_up) from err From 7d2e573b67f955428a6e7b1a6f183cd36f4d928a Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Mon, 22 May 2023 14:29:34 -0400 Subject: [PATCH 135/150] feat: use issuer create and register upgrade Signed-off-by: Daniel Bluhm --- aries_cloudagent/anoncreds/issuer.py | 23 ++++++++++++++++--- aries_cloudagent/anoncreds/routes.py | 34 ++++++++++++---------------- 2 files changed, 35 insertions(+), 22 deletions(-) diff --git a/aries_cloudagent/anoncreds/issuer.py b/aries_cloudagent/anoncreds/issuer.py index f112ff6c15..d96c237c63 100644 --- a/aries_cloudagent/anoncreds/issuer.py +++ b/aries_cloudagent/anoncreds/issuer.py @@ -561,7 +561,6 @@ async def create_and_register_revocation_registry_definition( except AnoncredsError as err: raise AnonCredsIssuerError("Error creating revocation registry") from err - rev_reg_def_json = rev_reg_def.to_json() rev_reg_def = RevRegDef.from_native(rev_reg_def) public_tails_uri = self.get_public_tails_uri(rev_reg_def) @@ -572,6 +571,7 @@ async def create_and_register_revocation_registry_definition( ) rev_reg_def_id = result.rev_reg_def_id + rev_reg_def_json = rev_reg_def.to_json() try: async with self._profile.transaction() as txn: @@ -639,12 +639,13 @@ async def upload_tails_file(self, rev_reg_def: RevRegDef): if not upload_success: raise AnonCredsIssuerError( f"Tails file for rev reg for {rev_reg_def.cred_def_id} " - "failed to upload: {result}" + f"failed to upload: {result}" ) if rev_reg_def.value.tails_location != result: raise AnonCredsIssuerError( f"Tails file for rev reg for {rev_reg_def.cred_def_id} " - "uploaded to wrong location: {result}" + f"uploaded to wrong location: {result} " + f"(should have been {rev_reg_def.value.tails_location})" ) # TODO: do we need to set uri? something like.. # await self.set_tails_file_public_uri(profile, result) @@ -683,6 +684,22 @@ async def get_created_revocation_registry_definitions( # entry.name was stored as the credential_definition's ID return [entry.name for entry in rev_reg_defs] + async def get_created_revocation_registry_definition( + self, + rev_reg_def_id: str, + ) -> Optional[RevRegDef]: + """Retrieve rev reg def by ID from rev reg defs previously created.""" + async with self._profile.session() as session: + rev_reg_def_entry = await session.handle.fetch( + CATEGORY_REV_REG_DEF, + name=rev_reg_def_id, + ) + + if rev_reg_def_entry: + return RevRegDef.deserialize(rev_reg_def_entry.value_json) + + return None + async def create_and_register_revocation_list( self, rev_reg_def_id: str, options: Optional[dict] = None ): diff --git a/aries_cloudagent/anoncreds/routes.py b/aries_cloudagent/anoncreds/routes.py index 0ccfde3be2..ca80c2d6fe 100644 --- a/aries_cloudagent/anoncreds/routes.py +++ b/aries_cloudagent/anoncreds/routes.py @@ -27,7 +27,6 @@ from .issuer import AnonCredsIssuer, AnonCredsIssuerError from .models.anoncreds_cred_def import CredDefResultSchema, GetCredDefResultSchema from .models.anoncreds_revocation import ( - RevRegDef, RevRegDefResultSchema, RevListResultSchema, ) @@ -378,17 +377,18 @@ async def rev_reg_def_post(request: web.BaseRequest): ) try: - revoc = AnonCredsRevocation(context.profile) - issuer_rev_reg_rec = await revoc.init_issuer_registry( - issuer_id, - cred_def_id, - max_cred_num=max_cred_num, - options=options, - notify=False, + result = await shield( + issuer.create_and_register_revocation_registry_definition( + issuer_id, + cred_def_id, + registry_type="CL_ACCUM", + max_cred_num=max_cred_num, + tag="default", + options=options, + ) ) except RevocationNotSupportedError as e: raise web.HTTPBadRequest(reason=e.message) from e - result = await shield(issuer_rev_reg_rec.create_and_register_def(context.profile)) return web.json_response(result.serialize()) @@ -462,21 +462,17 @@ async def upload_tails_file(request: web.BaseRequest): """ context: AdminRequestContext = request["context"] profile: AskarProfile = context.profile - anoncreds_registry: AnonCredsRegistry = context.inject(AnonCredsRegistry) rev_reg_id = request.match_info["rev_reg_id"] try: issuer = AnonCredsIssuer(profile) - rev_reg_def_result = ( - await anoncreds_registry.get_revocation_registry_definition( - profile, rev_reg_id - ) + rev_reg_def = await issuer.get_created_revocation_registry_definition( + rev_reg_id ) - rev_reg_def: RevRegDef = rev_reg_def_result.revocation_registry - # TODO: Should we check if tails file exists - except StorageNotFoundError as err: # TODO: update error - raise web.HTTPNotFound(reason=err.roll_up) from err - try: + if rev_reg_def is None: + raise web.HTTPNotFound(reason="No rev reg def found") + await issuer.upload_tails_file(rev_reg_def) + except AnonCredsIssuerError as e: raise web.HTTPInternalServerError(reason=str(e)) from e From ac2776a74905db26bf6ea2a1d799b2a64c328ac4 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Mon, 22 May 2023 17:43:48 -0400 Subject: [PATCH 136/150] fix: tails location mismatch Signed-off-by: Daniel Bluhm --- aries_cloudagent/anoncreds/issuer.py | 10 +++++----- aries_cloudagent/anoncreds/routes.py | 12 ++++++------ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/aries_cloudagent/anoncreds/issuer.py b/aries_cloudagent/anoncreds/issuer.py index d96c237c63..6deafbed95 100644 --- a/aries_cloudagent/anoncreds/issuer.py +++ b/aries_cloudagent/anoncreds/issuer.py @@ -563,7 +563,7 @@ async def create_and_register_revocation_registry_definition( rev_reg_def = RevRegDef.from_native(rev_reg_def) - public_tails_uri = self.get_public_tails_uri(rev_reg_def) + public_tails_uri = self.generate_public_tails_uri(rev_reg_def) rev_reg_def.value.tails_location = public_tails_uri anoncreds_registry = self.profile.inject(AnonCredsRegistry) result = await anoncreds_registry.register_revocation_registry_definition( @@ -602,7 +602,7 @@ def _check_url(self, url) -> None: if not (parsed.scheme and parsed.netloc and parsed.path): raise AnonCredsRegistrationError("URI {} is not a valid URL".format(url)) - def get_public_tails_uri(self, rev_reg_def: RevRegDef): + def generate_public_tails_uri(self, rev_reg_def: RevRegDef): """Construct tails uri from rev_reg_def.""" tails_base_url = self._profile.settings.get("tails_server_base_url") if not tails_base_url: @@ -647,8 +647,6 @@ async def upload_tails_file(self, rev_reg_def: RevRegDef): f"uploaded to wrong location: {result} " f"(should have been {rev_reg_def.value.tails_location})" ) - # TODO: do we need to set uri? something like.. - # await self.set_tails_file_public_uri(profile, result) async def update_revocation_registry_definition_state( self, rev_reg_def_id: str, state: str @@ -720,10 +718,12 @@ async def create_and_register_revocation_list( ) rev_reg_def = RevRegDef.deserialize(rev_reg_def_entry.value_json) + # TODO This is a little rough; stored tails location will have public uri + rev_reg_def.value.tails_location = self.get_local_tails_path(rev_reg_def) rev_list = RevocationStatusList.create( rev_reg_def_id, - rev_reg_def_entry.raw_value, + rev_reg_def.to_native(), rev_reg_def.issuer_id, ) diff --git a/aries_cloudagent/anoncreds/routes.py b/aries_cloudagent/anoncreds/routes.py index ca80c2d6fe..d42f92da9b 100644 --- a/aries_cloudagent/anoncreds/routes.py +++ b/aries_cloudagent/anoncreds/routes.py @@ -21,7 +21,6 @@ from ..admin.request_context import AdminRequestContext from ..messaging.models.openapi import OpenAPISchema from ..messaging.valid import UUIDFour -from ..revocation.anoncreds import AnonCredsRevocation from ..revocation.error import RevocationNotSupportedError from ..storage.error import StorageNotFoundError from .issuer import AnonCredsIssuer, AnonCredsIssuerError @@ -429,12 +428,13 @@ async def rev_list_post(request: web.BaseRequest): rev_reg_def_id = body.get("revRegDefId") options = body.get("options") + issuer = AnonCredsIssuer(context.profile) try: - revoc = AnonCredsRevocation(context.profile) - rev_reg = await revoc.get_issuer_rev_reg_record(rev_reg_def_id) - result = await rev_reg.create_and_register_list( - context.profile, - options, + result = await shield( + issuer.create_and_register_revocation_list( + rev_reg_def_id, + options, + ) ) LOGGER.debug("published revocation list for: %s", rev_reg_def_id) From 81ab818cd4a2d5be142c4f4abb1bbf62386e5e14 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Tue, 23 May 2023 11:44:23 -0400 Subject: [PATCH 137/150] feat: issue revocable creds using anoncreds interface Signed-off-by: Daniel Bluhm --- aries_cloudagent/anoncreds/holder.py | 4 +- aries_cloudagent/anoncreds/issuer.py | 218 +++++++++++++++--- .../v2_0/formats/indy/handler.py | 76 +----- 3 files changed, 203 insertions(+), 95 deletions(-) diff --git a/aries_cloudagent/anoncreds/holder.py b/aries_cloudagent/anoncreds/holder.py index cde0f83c56..63158c63b2 100644 --- a/aries_cloudagent/anoncreds/holder.py +++ b/aries_cloudagent/anoncreds/holder.py @@ -85,7 +85,9 @@ async def get_master_secret(self) -> str: raise AnonCredsHolderError("Error fetching master secret") from err if record: try: - secret = record.raw_value + # TODO should be able to use raw_value but memoryview + # isn't accepted by cred.process + secret = record.value.decode("ascii") except AnoncredsError as err: raise AnonCredsHolderError( "Error loading master secret" diff --git a/aries_cloudagent/anoncreds/issuer.py b/aries_cloudagent/anoncreds/issuer.py index 6deafbed95..330e9cca74 100644 --- a/aries_cloudagent/anoncreds/issuer.py +++ b/aries_cloudagent/anoncreds/issuer.py @@ -1,11 +1,13 @@ """anoncreds-rs issuer implementation.""" import asyncio +import hashlib +import http import logging import os from pathlib import Path from time import time -from typing import NamedTuple, Optional, Sequence, Tuple +from typing import List, NamedTuple, Optional, Sequence, Tuple from urllib.parse import urlparse from anoncreds import ( @@ -19,6 +21,8 @@ Schema, ) from aries_askar import AskarError +import base58 +from requests import RequestException, Session from ..askar.profile import AskarProfile, AskarProfileSession from ..core.error import BaseError @@ -787,33 +791,106 @@ async def create_credential_offer(self, credential_definition_id: str) -> str: return credential_offer.to_json() - async def create_credential( + # TODO This is used by both issuer and holder; this should be moved + async def retrieve_tails(self, rev_reg_def: RevRegDef): + """Retrieve tails file from server.""" + LOGGER.info( + "Downloading the tails file with hash: %s", + rev_reg_def.value.tails_hash, + ) + + tails_file_path = Path(self.get_local_tails_path(rev_reg_def)) + tails_file_dir = tails_file_path.parent + if not tails_file_dir.exists(): + tails_file_dir.mkdir(parents=True) + + buffer_size = 65536 # should be multiple of 32 bytes for sha256 + file_hasher = hashlib.sha256() + with open(tails_file_path, "wb", buffer_size) as tails_file: + with Session() as req_session: + try: + resp = req_session.get( + rev_reg_def.value.tails_location, stream=True + ) + # Should this directly raise an Error? + if resp.status_code != http.HTTPStatus.OK: + LOGGER.warning( + f"Unexpected status code for tails file: {resp.status_code}" + ) + for buf in resp.iter_content(chunk_size=buffer_size): + tails_file.write(buf) + file_hasher.update(buf) + except RequestException as rx: + raise AnonCredsIssuerError(f"Error retrieving tails file: {rx}") + + download_tails_hash = base58.b58encode(file_hasher.digest()).decode("utf-8") + if download_tails_hash != rev_reg_def.value.tails_hash: + try: + os.remove(tails_file_path) + except OSError as err: + LOGGER.warning(f"Could not delete invalid tails file: {err}") + + raise AnonCredsIssuerError( + "The hash of the downloaded tails file does not match." + ) + + return tails_file_path + + # TODO This is used by both issuer and holder; this should be moved + async def get_or_fetch_local_tails_path(self, rev_reg_def: RevRegDef): + """Return path to local tails file. + + If not present, retrieve from tails server. + """ + tails_file_path = self.get_local_tails_path(rev_reg_def) + if Path(tails_file_path).is_file(): + return tails_file_path + return await self.retrieve_tails(rev_reg_def) + + async def handle_full_registry(self, rev_reg_def_id: str): + """Update the registry status and start the next registry generation.""" + # TODO + + async def get_or_create_active_registry(self, cred_def_id: str) -> RevRegDefResult: + """Get or create a revocation registry for the given cred def id.""" + async with self._profile.session() as session: + rev_reg_defs = await session.handle.fetch_all( + CATEGORY_REV_REG_DEF, + { + "cred_def_id": cred_def_id, + }, + ) + + if not rev_reg_defs: + raise AnonCredsIssuerError("No active registry") + + # TODO Sort and return most recent + # TODO Create a registry if none available + entry = rev_reg_defs[0] + + rev_reg_def = RevRegDef.deserialize(entry.value_json) + result = RevRegDefResult( + None, + RevRegDefState( + state=STATE_FINISHED, + revocation_registry_definition_id=entry.name, + revocation_registry_definition=rev_reg_def, + ), + registration_metadata={}, + revocation_registry_definition_metadata={}, + ) + return result + + async def _create_credential( self, - schema_id: str, + credential_definition_id: str, + schema_attributes: List[str], credential_offer: dict, credential_request: dict, credential_values: dict, - revoc_reg_id: Optional[str] = None, + rev_reg_def_id: Optional[str] = None, tails_file_path: Optional[str] = None, ) -> Tuple[str, str]: - """ - Create a credential. - - Args - schema_id: Schema ID to create credential for - credential_offer: Credential Offer to create credential for - credential_request: Credential request to create credential for - credential_values: Values to go in credential - revoc_reg_id: ID of the revocation registry - tails_file_path: The location of the tails file - - Returns: - A tuple of created credential and revocation id - - """ - anoncreds_registry = self.profile.inject(AnonCredsRegistry) - schema_result = await anoncreds_registry.get_schema(self.profile, schema_id) - credential_definition_id = credential_offer["cred_def_id"] try: async with self._profile.session() as session: cred_def = await session.handle.fetch( @@ -832,7 +909,6 @@ async def create_credential( ) raw_values = {} - schema_attributes = schema_result.schema.attr_names for attribute in schema_attributes: # Ensure every attribute present in schema to be set. # Extraneous attribute names are ignored. @@ -846,18 +922,18 @@ async def create_credential( raw_values[attribute] = str(credential_value) - if revoc_reg_id: + if rev_reg_def_id and tails_file_path: try: async with self._profile.transaction() as txn: - rev_list = await txn.handle.fetch(CATEGORY_REV_LIST, revoc_reg_id) + rev_list = await txn.handle.fetch(CATEGORY_REV_LIST, rev_reg_def_id) rev_reg_info = await txn.handle.fetch( - CATEGORY_REV_REG_INFO, revoc_reg_id, for_update=True + CATEGORY_REV_REG_INFO, rev_reg_def_id, for_update=True ) rev_reg_def = await txn.handle.fetch( - CATEGORY_REV_REG_DEF, revoc_reg_id + CATEGORY_REV_REG_DEF, rev_reg_def_id ) rev_key = await txn.handle.fetch( - CATEGORY_REV_REG_DEF_PRIVATE, revoc_reg_id + CATEGORY_REV_REG_DEF_PRIVATE, rev_reg_def_id ) if not rev_list: raise AnonCredsIssuerError("Revocation registry not found") @@ -895,7 +971,7 @@ async def create_credential( ) rev_info["curr_id"] = rev_reg_index await txn.handle.replace( - CATEGORY_REV_REG_INFO, revoc_reg_id, value_json=rev_info + CATEGORY_REV_REG_INFO, rev_reg_def_id, value_json=rev_info ) await txn.commit() except AskarError as err: @@ -925,7 +1001,7 @@ async def create_credential( credential_request, raw_values, None, - revoc_reg_id, + rev_reg_def_id, rev_list, revoc, ), @@ -935,6 +1011,88 @@ async def create_credential( return credential.to_json(), credential_revocation_id + async def create_credential( + self, + credential_offer: dict, + credential_request: dict, + credential_values: dict, + *, + retries: int = 5, + ) -> Tuple[str, str, str]: + """ + Create a credential. + + Args + credential_offer: Credential Offer to create credential for + credential_request: Credential request to create credential for + credential_values: Values to go in credential + revoc_reg_id: ID of the revocation registry + retries: number of times to retry credential creation + + Returns: + A tuple of created credential and revocation id + + """ + anoncreds_registry = self.profile.inject(AnonCredsRegistry) + schema_id = credential_offer["schema_id"] + schema_result = await anoncreds_registry.get_schema(self.profile, schema_id) + cred_def_id = credential_offer["cred_def_id"] + + revocable = await self.cred_def_supports_revocation(cred_def_id) + + for attempt in range(max(retries, 1)): + if attempt > 0: + LOGGER.info( + "Waiting 2s before retrying credential issuance for cred def '%s'", + cred_def_id, + ) + await asyncio.sleep(2) + + rev_reg_def_result = None + if revocable: + rev_reg_def_result = await self.get_or_create_active_registry( + cred_def_id + ) + if ( + rev_reg_def_result.revocation_registry_definition_state.state + != STATE_FINISHED + ): + continue + rev_reg_def_id = rev_reg_def_result.rev_reg_def_id + tails_file_path = self.get_local_tails_path( + rev_reg_def_result.rev_reg_def + ) + else: + rev_reg_def_id = None + tails_file_path = None + + try: + cred_json, cred_rev_id = await self._create_credential( + cred_def_id, + schema_result.schema_value.attr_names, + credential_offer, + credential_request, + credential_values, + rev_reg_def_id, + tails_file_path, + ) + except AnonCredsIssuerRevocationRegistryFullError: + # unlucky, another instance filled the registry first + continue + + if ( + rev_reg_def_result + and rev_reg_def_result.rev_reg_def.value.max_cred_num + <= int(cred_rev_id) + ): + await self.handle_full_registry(rev_reg_def_id) + + return cred_json, cred_rev_id, rev_reg_def_id + + raise AnonCredsIssuerError( + f"Cred def '{cred_def_id}' has no active revocation registry" + ) + async def revoke_credentials( self, revoc_reg_id: str, diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/handler.py b/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/handler.py index a46b0285f9..155a0d7d17 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/handler.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/handler.py @@ -1,6 +1,5 @@ """V2.0 issue-credential indy credential format handler.""" -import asyncio import json import logging from typing import Mapping, Tuple @@ -11,7 +10,6 @@ from ......anoncreds.holder import AnonCredsHolder, AnonCredsHolderError from ......anoncreds.issuer import ( AnonCredsIssuer, - AnonCredsIssuerRevocationRegistryFullError, ) from ......anoncreds.models.cred import IndyCredentialSchema from ......anoncreds.models.cred_abstract import IndyCredAbstractSchema @@ -28,9 +26,7 @@ ) from ......messaging.decorators.attach_decorator import AttachDecorator from ......multitenant.base import BaseMultitenantManager -from ......revocation.anoncreds import AnonCredsRevocation from ......revocation.models.issuer_cred_rev_record import IssuerCredRevRecord -from ......revocation.models.revocation_registry import RevocationRegistry from ......storage.base import BaseStorage from ...message_types import ( ATTACHMENT_FORMAT, @@ -327,83 +323,35 @@ async def issue_credential( 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"] issuer = AnonCredsIssuer(self.profile) - revocable = await issuer.cred_def_supports_revocation(cred_def_id) - result = None - - for attempt in range(max(retries, 1)): - if attempt > 0: - LOGGER.info( - "Waiting 2s before retrying credential issuance for cred def '%s'", - cred_def_id, - ) - await asyncio.sleep(2) - - if revocable: - # TODO make this go through the anoncreds interface - revoc = AnonCredsRevocation(self.profile) - registry_info = await revoc.get_or_create_active_registry(cred_def_id) - if not registry_info: - continue - del revoc - issuer_rev_reg, rev_reg = registry_info - rev_reg_id = issuer_rev_reg.revoc_reg_id - tails_path = rev_reg.tails_local_path - else: - rev_reg_id = None - tails_path = None - - try: - (cred_json, cred_rev_id) = await issuer.create_credential( - schema_id, - cred_offer, - cred_request, - cred_values, - rev_reg_id, - tails_path, - ) - except AnonCredsIssuerRevocationRegistryFullError: - # unlucky, another instance filled the registry first - continue - - if revocable and rev_reg.max_creds <= int(cred_rev_id): - revoc = AnonCredsRevocation(self.profile) - await revoc.handle_full_registry(rev_reg_id) - del revoc - - result = self.get_format_data(CRED_20_ISSUE, json.loads(cred_json)) - break - - if not result: - raise V20CredFormatError( - f"Cred def '{cred_def_id}' has no active revocation registry" - ) + cred_json, cred_rev_id, rev_reg_def_id = await issuer.create_credential( + cred_offer, cred_request, cred_values + ) + result = self.get_format_data(CRED_20_ISSUE, json.loads(cred_json)) async with self._profile.transaction() as txn: detail_record = V20CredExRecordIndy( cred_ex_id=cred_ex_record.cred_ex_id, - rev_reg_id=rev_reg_id, + rev_reg_id=rev_reg_def_id, cred_rev_id=cred_rev_id, ) await detail_record.save(txn, reason="v2.0 issue credential") - if revocable and cred_rev_id: + if cred_rev_id: issuer_cr_rec = IssuerCredRevRecord( state=IssuerCredRevRecord.STATE_ISSUED, cred_ex_id=cred_ex_record.cred_ex_id, cred_ex_version=IssuerCredRevRecord.VERSION_2, - rev_reg_id=rev_reg_id, + rev_reg_id=rev_reg_def_id, cred_rev_id=cred_rev_id, ) await issuer_cr_rec.save( txn, reason=( "Created issuer cred rev record for " - f"rev reg id {rev_reg_id}, index {cred_rev_id}" + f"rev reg id {rev_reg_def_id}, index {cred_rev_id}" ), ) await txn.commit() @@ -435,7 +383,7 @@ async def store_credential( self.profile, cred["rev_reg_id"] ) ) - rev_reg_def = rev_reg_def_result.revocation_registry.serialize() + rev_reg_def = rev_reg_def_result.revocation_registry holder = AnonCredsHolder(self.profile) cred_offer_message = cred_ex_record.cred_offer @@ -444,8 +392,8 @@ async def store_credential( mime_types = cred_offer_message.credential_preview.mime_types() or None if rev_reg_def: - rev_reg = RevocationRegistry.from_definition(rev_reg_def, True) - await rev_reg.get_or_fetch_local_tails_path() + issuer = AnonCredsIssuer(self.profile) + await issuer.get_or_fetch_local_tails_path(rev_reg_def) try: detail_record = await self.get_detail_record(cred_ex_record.cred_ex_id) if detail_record is None: @@ -459,7 +407,7 @@ async def store_credential( detail_record.cred_request_metadata, mime_types, credential_id=cred_id, - rev_reg_def=rev_reg_def, + rev_reg_def=rev_reg_def.serialize() if rev_reg_def else None, ) detail_record.cred_id_stored = cred_id_stored From 52e12ffc72f74f575236cfa7503f3d74a20b044d Mon Sep 17 00:00:00 2001 From: Adam Burdett Date: Wed, 24 May 2023 11:28:36 -0600 Subject: [PATCH 138/150] fix(anoncreds): proof, rev reg def id Signed-off-by: Adam Burdett --- .../present_proof/indy/pres_exch_handler.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/aries_cloudagent/protocols/present_proof/indy/pres_exch_handler.py b/aries_cloudagent/protocols/present_proof/indy/pres_exch_handler.py index c6afa0b901..f183ac18e4 100644 --- a/aries_cloudagent/protocols/present_proof/indy/pres_exch_handler.py +++ b/aries_cloudagent/protocols/present_proof/indy/pres_exch_handler.py @@ -120,14 +120,17 @@ async def _get_ledger_objects(self, credentials: dict): if credential.get("rev_reg_id"): revocation_registry_id = credential["rev_reg_id"] if revocation_registry_id not in revocation_registries: + rev_reg = ( + await anoncreds_registry.get_revocation_registry_definition( + self._profile, revocation_registry_id + ) + ).revocation_registry.serialize() + # add id to the serialized rev_reg + rev_reg["id"] = revocation_registry_id revocation_registries[ revocation_registry_id ] = RevocationRegistry.from_definition( - ( - await anoncreds_registry.get_revocation_registry_definition( - self._profile, revocation_registry_id - ) - ).revocation_registry.serialize(), + rev_reg, True, ) return schemas, cred_defs, revocation_registries From bfacecd2b24cec9deb5168cda616497bc97453f7 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Thu, 25 May 2023 22:26:40 -0600 Subject: [PATCH 139/150] fix: remove redundant get_revocation_registry_definitions() function defintion Signed-off-by: Char Howland --- aries_cloudagent/anoncreds/default/legacy_indy/registry.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/aries_cloudagent/anoncreds/default/legacy_indy/registry.py b/aries_cloudagent/anoncreds/default/legacy_indy/registry.py index 6063c8152c..b8646c864f 100644 --- a/aries_cloudagent/anoncreds/default/legacy_indy/registry.py +++ b/aries_cloudagent/anoncreds/default/legacy_indy/registry.py @@ -402,9 +402,6 @@ async def get_revocation_registry_definition( return result - async def get_revocation_registry_definitions(self, profile: Profile, filter: str): - """Get credential definition ids filtered by filter""" - async def register_revocation_registry_definition( self, profile: Profile, From 8a699bc770db9c72fe46f3674e767e14a5f5aeb1 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Fri, 26 May 2023 13:35:21 -0400 Subject: [PATCH 140/150] refactor: use anoncreds models in get_ledger_objects Signed-off-by: Daniel Bluhm --- aries_cloudagent/anoncreds/holder.py | 20 ++++++---- aries_cloudagent/anoncreds/issuer.py | 6 +-- .../present_proof/indy/pres_exch_handler.py | 38 ++++++++++--------- 3 files changed, 36 insertions(+), 28 deletions(-) diff --git a/aries_cloudagent/anoncreds/holder.py b/aries_cloudagent/anoncreds/holder.py index 63158c63b2..3e3a56ab00 100644 --- a/aries_cloudagent/anoncreds/holder.py +++ b/aries_cloudagent/anoncreds/holder.py @@ -4,21 +4,21 @@ import json import logging import re -import uuid from typing import Dict, Optional, Sequence, Tuple, Union +import uuid from anoncreds import ( AnoncredsError, Credential, CredentialRequest, CredentialRevocationState, - Presentation, PresentCredentials, + Presentation, ) from anoncreds.bindings import create_link_secret - from aries_askar import AskarError, AskarErrorCode +from ..anoncreds.models.anoncreds_schema import AnonCredsSchema from ..askar.profile import AskarProfile from ..core.error import BaseError from ..ledger.base import BaseLedger @@ -474,8 +474,8 @@ async def create_presentation( self, presentation_request: dict, requested_credentials: dict, - schemas: dict, - credential_definitions: dict, + schemas: Dict[str, AnonCredsSchema], + credential_definitions: Dict[str, CredDef], rev_states: dict = None, ) -> str: """ @@ -550,8 +550,14 @@ def get_rev_state(cred_id: str, detail: dict): present_creds, self_attest, secret, - schemas, - credential_definitions, + { + schema_id: schema.to_native() + for schema_id, schema in schemas.items() + }, + { + cred_def_id: cred_def.to_native() + for cred_def_id, cred_def in credential_definitions.items() + }, ) except AnoncredsError as err: raise AnonCredsHolderError("Error creating presentation") from err diff --git a/aries_cloudagent/anoncreds/issuer.py b/aries_cloudagent/anoncreds/issuer.py index 330e9cca74..2d72d603f4 100644 --- a/aries_cloudagent/anoncreds/issuer.py +++ b/aries_cloudagent/anoncreds/issuer.py @@ -792,7 +792,7 @@ async def create_credential_offer(self, credential_definition_id: str) -> str: return credential_offer.to_json() # TODO This is used by both issuer and holder; this should be moved - async def retrieve_tails(self, rev_reg_def: RevRegDef): + async def retrieve_tails(self, rev_reg_def: RevRegDef) -> str: """Retrieve tails file from server.""" LOGGER.info( "Downloading the tails file with hash: %s", @@ -834,10 +834,10 @@ async def retrieve_tails(self, rev_reg_def: RevRegDef): "The hash of the downloaded tails file does not match." ) - return tails_file_path + return str(tails_file_path) # TODO This is used by both issuer and holder; this should be moved - async def get_or_fetch_local_tails_path(self, rev_reg_def: RevRegDef): + async def get_or_fetch_local_tails_path(self, rev_reg_def: RevRegDef) -> str: """Return path to local tails file. If not present, retrieve from tails server. diff --git a/aries_cloudagent/protocols/present_proof/indy/pres_exch_handler.py b/aries_cloudagent/protocols/present_proof/indy/pres_exch_handler.py index f183ac18e4..28318cff4d 100644 --- a/aries_cloudagent/protocols/present_proof/indy/pres_exch_handler.py +++ b/aries_cloudagent/protocols/present_proof/indy/pres_exch_handler.py @@ -2,14 +2,18 @@ import json import logging import time -from typing import Union +from typing import Dict, Tuple, Union + +from aries_cloudagent.anoncreds.issuer import AnonCredsIssuer +from aries_cloudagent.anoncreds.models.anoncreds_cred_def import CredDef -from ....anoncreds.registry import AnonCredsRegistry from ....anoncreds.holder import AnonCredsHolder, AnonCredsHolderError +from ....anoncreds.models.anoncreds_revocation import RevRegDef +from ....anoncreds.models.anoncreds_schema import AnonCredsSchema from ....anoncreds.models.xform import indy_proof_req2non_revoc_intervals +from ....anoncreds.registry import AnonCredsRegistry from ....core.error import BaseError from ....core.profile import Profile -from ....revocation.models.revocation_registry import RevocationRegistry from ..v1_0.models.presentation_exchange import V10PresentationExchange from ..v2_0.messages.pres_format import V20PresFormat from ..v2_0.models.pres_exchange import V20PresExRecord @@ -97,7 +101,9 @@ def _remove_superfluous_timestamps(self, requested_credentials, credentials): f"{reft} for non-revocable credential {req_item['cred_id']}" ) - async def _get_ledger_objects(self, credentials: dict): + async def _get_ledger_objects( + self, credentials: dict + ) -> Tuple[Dict[str, AnonCredsSchema], Dict[str, CredDef], Dict[str, RevRegDef]]: """Get all schemas, credential definitions, and revocation registries in use""" schemas = {} cred_defs = {} @@ -109,14 +115,14 @@ async def _get_ledger_objects(self, credentials: dict): if schema_id not in schemas: schemas[schema_id] = ( await anoncreds_registry.get_schema(self._profile, schema_id) - ).schema.serialize() + ).schema cred_def_id = credential["cred_def_id"] if cred_def_id not in cred_defs: cred_defs[cred_def_id] = ( await anoncreds_registry.get_credential_definition( self._profile, cred_def_id ) - ).credential_definition.serialize() + ).credential_definition if credential.get("rev_reg_id"): revocation_registry_id = credential["rev_reg_id"] if revocation_registry_id not in revocation_registries: @@ -124,15 +130,9 @@ async def _get_ledger_objects(self, credentials: dict): await anoncreds_registry.get_revocation_registry_definition( self._profile, revocation_registry_id ) - ).revocation_registry.serialize() - # add id to the serialized rev_reg - rev_reg["id"] = revocation_registry_id - revocation_registries[ - revocation_registry_id - ] = RevocationRegistry.from_definition( - rev_reg, - True, - ) + ).revocation_registry + revocation_registries[revocation_registry_id] = rev_reg + return schemas, cred_defs, revocation_registries async def _get_revocation_lists(self, requested_referents: dict, credentials: dict): @@ -192,13 +192,15 @@ async def _get_revocation_states( ) in rev_lists.values(): if rev_reg_id not in revocation_states: revocation_states[rev_reg_id] = {} - rev_reg = revocation_registries[rev_reg_id] - tails_local_path = await rev_reg.get_or_fetch_local_tails_path() + rev_reg_def = revocation_registries[rev_reg_id] + # TODO Not an issuer specific operation to fetch tails + issuer = AnonCredsIssuer(self._profile) + tails_local_path = await issuer.get_or_fetch_local_tails_path(rev_reg_def) try: revocation_states[rev_reg_id][timestamp] = json.loads( await self.holder.create_revocation_state( credentials[credential_id]["cred_rev_id"], - rev_reg.reg_def, + rev_reg_def.serialize(), rev_list, tails_local_path, ) From aa87a45b3bd48e4bb45bfaf027418af09d26d960 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Mon, 29 May 2023 15:32:02 -0400 Subject: [PATCH 141/150] refactor: split rev methods from issuer Signed-off-by: Daniel Bluhm --- aries_cloudagent/anoncreds/holder.py | 26 +- aries_cloudagent/anoncreds/issuer.py | 744 +--------------- aries_cloudagent/anoncreds/revocation.py | 842 ++++++++++++++++++ aries_cloudagent/anoncreds/routes.py | 69 +- .../issue_credential/v1_0/manager.py | 22 +- .../v2_0/formats/indy/handler.py | 21 +- .../v2_0/formats/indy/tests/test_handler.py | 45 +- .../present_proof/indy/pres_exch_handler.py | 12 +- .../models/issuer_rev_reg_record.py | 29 +- 9 files changed, 975 insertions(+), 835 deletions(-) create mode 100644 aries_cloudagent/anoncreds/revocation.py diff --git a/aries_cloudagent/anoncreds/holder.py b/aries_cloudagent/anoncreds/holder.py index 3e3a56ab00..eb2e1d6c4a 100644 --- a/aries_cloudagent/anoncreds/holder.py +++ b/aries_cloudagent/anoncreds/holder.py @@ -21,6 +21,7 @@ from ..anoncreds.models.anoncreds_schema import AnonCredsSchema from ..askar.profile import AskarProfile from ..core.error import BaseError +from ..core.profile import Profile from ..ledger.base import BaseLedger from ..wallet.error import WalletNotFoundError from .models.anoncreds_cred_def import CredDef @@ -57,7 +58,7 @@ class AnonCredsHolder: MASTER_SECRET_ID = "default" - def __init__(self, profile: AskarProfile): + def __init__(self, profile: Profile): """ Initialize an AnonCredsHolder instance. @@ -68,15 +69,18 @@ def __init__(self, profile: AskarProfile): self._profile = profile @property - def profile(self): + def profile(self) -> AskarProfile: """Accessor for the profile instance.""" + if not isinstance(self._profile, AskarProfile): + raise ValueError("AnonCreds interface requires Askar") + return self._profile async def get_master_secret(self) -> str: """Get or create the default master secret.""" while True: - async with self._profile.session() as session: + async with self.profile.session() as session: try: record = await session.handle.fetch( CATEGORY_MASTER_SECRET, AnonCredsHolder.MASTER_SECRET_ID @@ -237,7 +241,7 @@ async def store_credential( mime_types[k] = credential_attr_mime_types[k] try: - async with self._profile.transaction() as txn: + async with self.profile.transaction() as txn: await txn.handle.insert( CATEGORY_CREDENTIAL, credential_id, @@ -270,12 +274,12 @@ async def get_credentials(self, start: int, count: int, wql: dict): result = [] try: - rows = self._profile.store.scan( + rows = self.profile.store.scan( CATEGORY_CREDENTIAL, wql, start, count, - self._profile.settings.get("wallet.askar_profile"), + self.profile.settings.get("wallet.askar_profile"), ) async for row in rows: cred = Credential.load(row.raw_value) @@ -344,12 +348,12 @@ async def get_credentials_for_presentation_request_by_referent( if extra_query: tag_filter = {"$and": [tag_filter, extra_query]} - rows = self._profile.store.scan( + rows = self.profile.store.scan( CATEGORY_CREDENTIAL, tag_filter, start, count, - self._profile.settings.get("wallet.askar_profile"), + self.profile.settings.get("wallet.askar_profile"), ) async for row in rows: if row.name in creds: @@ -383,7 +387,7 @@ async def get_credential(self, credential_id: str) -> str: async def _get_credential(self, credential_id: str) -> Credential: """Get an unencoded Credential instance from the store.""" try: - async with self._profile.session() as session: + async with self.profile.session() as session: cred = await session.handle.fetch(CATEGORY_CREDENTIAL, credential_id) except AskarError as err: raise AnonCredsHolderError("Error retrieving credential") from err @@ -431,7 +435,7 @@ async def delete_credential(self, credential_id: str): """ try: - async with self._profile.session() as session: + async with self.profile.session() as session: await session.handle.remove(CATEGORY_CREDENTIAL, credential_id) await session.handle.remove( AnonCredsHolder.RECORD_TYPE_MIME_TYPES, credential_id @@ -457,7 +461,7 @@ async def get_mime_type( """ try: - async with self._profile.session() as session: + async with self.profile.session() as session: mime_types_record = await session.handle.fetch( AnonCredsHolder.RECORD_TYPE_MIME_TYPES, credential_id, diff --git a/aries_cloudagent/anoncreds/issuer.py b/aries_cloudagent/anoncreds/issuer.py index 2d72d603f4..968e5ac905 100644 --- a/aries_cloudagent/anoncreds/issuer.py +++ b/aries_cloudagent/anoncreds/issuer.py @@ -1,43 +1,26 @@ """anoncreds-rs issuer implementation.""" import asyncio -import hashlib -import http import logging -import os -from pathlib import Path from time import time -from typing import List, NamedTuple, Optional, Sequence, Tuple -from urllib.parse import urlparse +from typing import Optional, Sequence from anoncreds import ( AnoncredsError, Credential, CredentialDefinition, CredentialOffer, - CredentialRevocationConfig, - RevocationRegistryDefinition, - RevocationStatusList, Schema, ) from aries_askar import AskarError -import base58 -from requests import RequestException, Session from ..askar.profile import AskarProfile, AskarProfileSession from ..core.error import BaseError -from ..tails.base import BaseTailsServer -from .base import AnonCredsRegistrationError, AnonCredsSchemaAlreadyExists +from ..core.profile import Profile +from .base import AnonCredsSchemaAlreadyExists from .models.anoncreds_cred_def import CredDef, CredDefResult -from .models.anoncreds_revocation import ( - RevList, - RevRegDef, - RevRegDefResult, - RevRegDefState, -) from .models.anoncreds_schema import AnonCredsSchema, SchemaResult, SchemaState from .registry import AnonCredsRegistry -from .util import indy_client_dir LOGGER = logging.getLogger(__name__) @@ -47,18 +30,11 @@ CATEGORY_CRED_DEF = "credential_def" CATEGORY_CRED_DEF_PRIVATE = "credential_def_private" CATEGORY_CRED_DEF_KEY_PROOF = "credential_def_key_proof" -CATEGORY_REV_LIST = "revocation_list" -CATEGORY_REV_REG_INFO = "revocation_reg_info" -CATEGORY_REV_REG_DEF = "revocation_reg_def" -CATEGORY_REV_REG_DEF_PRIVATE = "revocation_reg_def_private" -CATEGORY_REV_REG_ISSUER = "revocation_reg_def_issuer" STATE_FINISHED = "finished" EVENT_PREFIX = "acapy::anoncreds::" EVENT_SCHEMA = EVENT_PREFIX + CATEGORY_SCHEMA EVENT_CRED_DEF = EVENT_PREFIX + CATEGORY_CRED_DEF -EVENT_REV_REG_DEF = EVENT_PREFIX + CATEGORY_REV_REG_DEF -EVENT_REV_LIST = EVENT_PREFIX + CATEGORY_REV_LIST EVENT_FINISHED_SUFFIX = "::" + STATE_FINISHED @@ -66,16 +42,6 @@ class AnonCredsIssuerError(BaseError): """Generic issuer error.""" -class AnonCredsIssuerRevocationRegistryFullError(AnonCredsIssuerError): - """Revocation registry is full when issuing a new credential.""" - - -class RevokeResult(NamedTuple): - prev: Optional[RevocationStatusList] = None - curr: Optional[RevocationStatusList] = None - failed: Optional[Sequence[str]] = None - - class AnonCredsIssuer: """AnonCreds issuer class. @@ -107,7 +73,7 @@ class AnonCredsIssuer: is removed and an entry matching the registered id is added. """ - def __init__(self, profile: AskarProfile): + def __init__(self, profile: Profile): """ Initialize an AnonCredsIssuer instance. @@ -120,12 +86,15 @@ def __init__(self, profile: AskarProfile): @property def profile(self) -> AskarProfile: """Accessor for the profile instance.""" + if not isinstance(self._profile, AskarProfile): + raise ValueError("AnonCreds interface requires Askar") + return self._profile async def _update_entry_state(self, category: str, name: str, state: str): """Update the state tag of an entry in a given category.""" try: - async with self._profile.transaction() as txn: + async with self.profile.transaction() as txn: entry = await txn.handle.fetch( category, name, @@ -178,7 +147,7 @@ async def _store_schema( raise ValueError("Schema id or job id must be set") try: - async with self._profile.session() as session: + async with self.profile.session() as session: await session.handle.insert( CATEGORY_SCHEMA, ident, @@ -215,7 +184,7 @@ async def create_and_register_schema( """ # Check if record of a similar schema already exists in our records - async with self._profile.session() as session: + async with self.profile.session() as session: # TODO scan? schemas = await session.handle.fetch_all( CATEGORY_SCHEMA, @@ -235,7 +204,7 @@ async def create_and_register_schema( schema = Schema.create(name, version, issuer_id, attr_names) try: - anoncreds_registry = self._profile.inject(AnonCredsRegistry) + anoncreds_registry = self.profile.inject(AnonCredsRegistry) schema_result = await anoncreds_registry.register_schema( self.profile, AnonCredsSchema.from_native(schema), @@ -279,7 +248,7 @@ async def get_created_schemas( issuer_id: Optional[str] = None, ) -> Sequence[str]: """Retrieve IDs of schemas previously created.""" - async with self._profile.session() as session: + async with self.profile.session() as session: # TODO limit? scan? schemas = await session.handle.fetch_all( CATEGORY_SCHEMA, @@ -307,7 +276,7 @@ async def credential_definition_in_wallet( credential_definition_id: The credential definition ID to check """ try: - async with self._profile.session() as session: + async with self.profile.session() as session: return ( await session.handle.fetch( CATEGORY_CRED_DEF_PRIVATE, credential_definition_id @@ -340,7 +309,7 @@ async def create_and_register_credential_definition( CredDefResult: the result of the credential definition creation """ - anoncreds_registry = self._profile.inject(AnonCredsRegistry) + anoncreds_registry = self.profile.inject(AnonCredsRegistry) schema_result = await anoncreds_registry.get_schema(self.profile, schema_id) options = options or {} @@ -383,7 +352,7 @@ async def create_and_register_credential_definition( raise AnonCredsIssuerError("cred def id or job id required") try: - async with self._profile.transaction() as txn: + async with self.profile.transaction() as txn: await txn.handle.insert( CATEGORY_CRED_DEF, ident, @@ -434,7 +403,7 @@ async def get_created_credential_definitions( epoch: Optional[str] = None, ) -> Sequence[str]: """Retrieve IDs of credential definitions previously created.""" - async with self._profile.session() as session: + async with self.profile.session() as session: # TODO limit? scan? credential_definition_entries = await session.handle.fetch_all( CATEGORY_CRED_DEF, @@ -466,7 +435,7 @@ async def match_created_credential_definitions( epoch: Optional[str] = None, ) -> Optional[str]: """Return cred def id of most recent matching cred def.""" - async with self._profile.session() as session: + async with self.profile.session() as session: # TODO limit? scan? if cred_def_id: cred_def_entry = await session.handle.fetch( @@ -507,247 +476,6 @@ async def cred_def_supports_revocation(self, cred_def_id: str) -> bool: ) return cred_def_result.credential_definition.value.revocation is not None - async def create_and_register_revocation_registry_definition( - self, - issuer_id: str, - cred_def_id: str, - registry_type: str, - tag: str, - max_cred_num: int, - options: Optional[dict] = None, - ) -> RevRegDefResult: - """ - Create a new revocation registry and register on network. - - Args: - issuer_id (str): issuer identifier - cred_def_id (str): credential definition identifier - registry_type (str): revocation registry type - tag (str): revocation registry tag - max_cred_num (int): maximum number of credentials supported - options (dict): revocation registry options - - Returns: - RevRegDefResult: revocation registry definition result - - """ - try: - async with self._profile.session() as session: - cred_def = await session.handle.fetch(CATEGORY_CRED_DEF, cred_def_id) - except AskarError as err: - raise AnonCredsIssuerError( - "Error retrieving credential definition" - ) from err - - if not cred_def: - raise AnonCredsIssuerError( - "Credential definition not found for revocation registry" - ) - - tails_dir = indy_client_dir("tails", create=True) - - try: - ( - rev_reg_def, - rev_reg_def_private, - ) = await asyncio.get_event_loop().run_in_executor( - None, - lambda: RevocationRegistryDefinition.create( - cred_def_id, - cred_def.raw_value, - issuer_id, - tag, - registry_type, - max_cred_num, - tails_dir_path=tails_dir, - ), - ) - except AnoncredsError as err: - raise AnonCredsIssuerError("Error creating revocation registry") from err - - rev_reg_def = RevRegDef.from_native(rev_reg_def) - - public_tails_uri = self.generate_public_tails_uri(rev_reg_def) - rev_reg_def.value.tails_location = public_tails_uri - anoncreds_registry = self.profile.inject(AnonCredsRegistry) - result = await anoncreds_registry.register_revocation_registry_definition( - self.profile, rev_reg_def, options - ) - - rev_reg_def_id = result.rev_reg_def_id - rev_reg_def_json = rev_reg_def.to_json() - - try: - async with self._profile.transaction() as txn: - await txn.handle.insert( - CATEGORY_REV_REG_INFO, - rev_reg_def_id, - value_json={"curr_id": 0, "used_ids": []}, - ) - await txn.handle.insert( - CATEGORY_REV_REG_DEF, - rev_reg_def_id, - rev_reg_def_json, - tags={"cred_def_id": cred_def_id}, - ) - await txn.handle.insert( - CATEGORY_REV_REG_DEF_PRIVATE, - rev_reg_def_id, - rev_reg_def_private.to_json_buffer(), - ) - await txn.commit() - except AskarError as err: - raise AnonCredsIssuerError("Error saving new revocation registry") from err - - return result - - def _check_url(self, url) -> None: - parsed = urlparse(url) - if not (parsed.scheme and parsed.netloc and parsed.path): - raise AnonCredsRegistrationError("URI {} is not a valid URL".format(url)) - - def generate_public_tails_uri(self, rev_reg_def: RevRegDef): - """Construct tails uri from rev_reg_def.""" - tails_base_url = self._profile.settings.get("tails_server_base_url") - if not tails_base_url: - raise AnonCredsRegistrationError("tails_server_base_url not configured") - - public_tails_uri = ( - tails_base_url.rstrip("/") + f"/{rev_reg_def.value.tails_hash}" - ) - - self._check_url(public_tails_uri) - return public_tails_uri - - def get_local_tails_path(self, rev_reg_def: RevRegDef) -> str: - """Get the local path to the tails file.""" - tails_dir = indy_client_dir("tails", create=False) - return os.path.join(tails_dir, rev_reg_def.value.tails_hash) - - 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) - if not tails_server: - raise AnonCredsIssuerError("Tails server not configured") - if not Path(self.get_local_tails_path(rev_reg_def)).is_file(): - raise AnonCredsIssuerError("Local tails file not found") - - (upload_success, result) = await tails_server.upload_tails_file( - self._profile.context, - rev_reg_def.value.tails_hash, - self.get_local_tails_path(rev_reg_def), - interval=0.8, - backoff=-0.5, - max_attempts=5, # heuristic: respect HTTP timeout - ) - if not upload_success: - raise AnonCredsIssuerError( - f"Tails file for rev reg for {rev_reg_def.cred_def_id} " - f"failed to upload: {result}" - ) - if rev_reg_def.value.tails_location != result: - raise AnonCredsIssuerError( - f"Tails file for rev reg for {rev_reg_def.cred_def_id} " - f"uploaded to wrong location: {result} " - f"(should have been {rev_reg_def.value.tails_location})" - ) - - async def update_revocation_registry_definition_state( - self, rev_reg_def_id: str, state: str - ): - """Update the state of a rev reg def.""" - await self._update_entry_state(CATEGORY_REV_REG_DEF, rev_reg_def_id, state) - - async def finish_revocation_registry_definition(self, rev_reg_def_id: str): - """Mark a rev reg def as finished.""" - await self.update_revocation_registry_definition_state( - rev_reg_def_id, RevRegDefState.STATE_FINISHED - ) - - async def get_created_revocation_registry_definitions( - self, - cred_def_id: Optional[str] = None, - state: Optional[str] = None, - ) -> Sequence[str]: - """Retrieve IDs of rev reg defs previously created.""" - async with self._profile.session() as session: - # TODO limit? scan? - rev_reg_defs = await session.handle.fetch_all( - CATEGORY_REV_REG_DEF, - { - key: value - for key, value in { - "cred_def_id": cred_def_id, - "state": state, - }.items() - if value is not None - }, - ) - # entry.name was stored as the credential_definition's ID - return [entry.name for entry in rev_reg_defs] - - async def get_created_revocation_registry_definition( - self, - rev_reg_def_id: str, - ) -> Optional[RevRegDef]: - """Retrieve rev reg def by ID from rev reg defs previously created.""" - async with self._profile.session() as session: - rev_reg_def_entry = await session.handle.fetch( - CATEGORY_REV_REG_DEF, - name=rev_reg_def_id, - ) - - if rev_reg_def_entry: - return RevRegDef.deserialize(rev_reg_def_entry.value_json) - - return None - - async def create_and_register_revocation_list( - self, rev_reg_def_id: str, options: Optional[dict] = None - ): - """Create and register a revocation list.""" - try: - async with self._profile.session() as session: - rev_reg_def_entry = await session.handle.fetch( - CATEGORY_REV_REG_DEF, rev_reg_def_id - ) - except AskarError as err: - raise AnonCredsIssuerError( - "Error retrieving credential definition" - ) from err - - if not rev_reg_def_entry: - raise AnonCredsIssuerError( - f"Revocation registry definition not found for id {rev_reg_def_id}" - ) - - rev_reg_def = RevRegDef.deserialize(rev_reg_def_entry.value_json) - # TODO This is a little rough; stored tails location will have public uri - rev_reg_def.value.tails_location = self.get_local_tails_path(rev_reg_def) - - rev_list = RevocationStatusList.create( - rev_reg_def_id, - rev_reg_def.to_native(), - rev_reg_def.issuer_id, - ) - - anoncreds_registry = self.profile.inject(AnonCredsRegistry) - result = await anoncreds_registry.register_revocation_list( - self.profile, rev_reg_def, RevList.from_native(rev_list), options - ) - - try: - async with self._profile.session() as session: - await session.handle.insert( - CATEGORY_REV_LIST, - rev_reg_def_id, - result.revocation_list_state.revocation_list.to_json(), - ) - except AskarError as err: - raise AnonCredsIssuerError("Error saving new revocation registry") from err - - return result - async def create_credential_offer(self, credential_definition_id: str) -> str: """ Create a credential offer for the given credential definition id. @@ -760,7 +488,7 @@ async def create_credential_offer(self, credential_definition_id: str) -> str: """ try: - async with self._profile.session() as session: + async with self.profile.session() as session: cred_def = await session.handle.fetch( CATEGORY_CRED_DEF, credential_definition_id ) @@ -791,118 +519,29 @@ async def create_credential_offer(self, credential_definition_id: str) -> str: return credential_offer.to_json() - # TODO This is used by both issuer and holder; this should be moved - async def retrieve_tails(self, rev_reg_def: RevRegDef) -> str: - """Retrieve tails file from server.""" - LOGGER.info( - "Downloading the tails file with hash: %s", - rev_reg_def.value.tails_hash, - ) - - tails_file_path = Path(self.get_local_tails_path(rev_reg_def)) - tails_file_dir = tails_file_path.parent - if not tails_file_dir.exists(): - tails_file_dir.mkdir(parents=True) - - buffer_size = 65536 # should be multiple of 32 bytes for sha256 - file_hasher = hashlib.sha256() - with open(tails_file_path, "wb", buffer_size) as tails_file: - with Session() as req_session: - try: - resp = req_session.get( - rev_reg_def.value.tails_location, stream=True - ) - # Should this directly raise an Error? - if resp.status_code != http.HTTPStatus.OK: - LOGGER.warning( - f"Unexpected status code for tails file: {resp.status_code}" - ) - for buf in resp.iter_content(chunk_size=buffer_size): - tails_file.write(buf) - file_hasher.update(buf) - except RequestException as rx: - raise AnonCredsIssuerError(f"Error retrieving tails file: {rx}") - - download_tails_hash = base58.b58encode(file_hasher.digest()).decode("utf-8") - if download_tails_hash != rev_reg_def.value.tails_hash: - try: - os.remove(tails_file_path) - except OSError as err: - LOGGER.warning(f"Could not delete invalid tails file: {err}") - - raise AnonCredsIssuerError( - "The hash of the downloaded tails file does not match." - ) - - return str(tails_file_path) - - # TODO This is used by both issuer and holder; this should be moved - async def get_or_fetch_local_tails_path(self, rev_reg_def: RevRegDef) -> str: - """Return path to local tails file. - - If not present, retrieve from tails server. - """ - tails_file_path = self.get_local_tails_path(rev_reg_def) - if Path(tails_file_path).is_file(): - return tails_file_path - return await self.retrieve_tails(rev_reg_def) - - async def handle_full_registry(self, rev_reg_def_id: str): - """Update the registry status and start the next registry generation.""" - # TODO - - async def get_or_create_active_registry(self, cred_def_id: str) -> RevRegDefResult: - """Get or create a revocation registry for the given cred def id.""" - async with self._profile.session() as session: - rev_reg_defs = await session.handle.fetch_all( - CATEGORY_REV_REG_DEF, - { - "cred_def_id": cred_def_id, - }, - ) - - if not rev_reg_defs: - raise AnonCredsIssuerError("No active registry") - - # TODO Sort and return most recent - # TODO Create a registry if none available - entry = rev_reg_defs[0] - - rev_reg_def = RevRegDef.deserialize(entry.value_json) - result = RevRegDefResult( - None, - RevRegDefState( - state=STATE_FINISHED, - revocation_registry_definition_id=entry.name, - revocation_registry_definition=rev_reg_def, - ), - registration_metadata={}, - revocation_registry_definition_metadata={}, - ) - return result - - async def _create_credential( + async def create_credential( self, - credential_definition_id: str, - schema_attributes: List[str], credential_offer: dict, credential_request: dict, credential_values: dict, - rev_reg_def_id: Optional[str] = None, - tails_file_path: Optional[str] = None, - ) -> Tuple[str, str]: + ) -> str: + anoncreds_registry = self.profile.inject(AnonCredsRegistry) + schema_id = credential_offer["schema_id"] + schema_result = await anoncreds_registry.get_schema(self.profile, schema_id) + cred_def_id = credential_offer["cred_def_id"] + schema_attributes = schema_result.schema_value.attr_names + try: - async with self._profile.session() as session: - cred_def = await session.handle.fetch( - CATEGORY_CRED_DEF, credential_definition_id - ) + async with self.profile.session() as session: + cred_def = await session.handle.fetch(CATEGORY_CRED_DEF, cred_def_id) cred_def_private = await session.handle.fetch( - CATEGORY_CRED_DEF_PRIVATE, credential_definition_id + CATEGORY_CRED_DEF_PRIVATE, cred_def_id ) except AskarError as err: raise AnonCredsIssuerError( "Error retrieving credential definition" ) from err + if not cred_def or not cred_def_private: raise AnonCredsIssuerError( "Credential definition not found for credential issuance" @@ -922,75 +561,6 @@ async def _create_credential( raw_values[attribute] = str(credential_value) - if rev_reg_def_id and tails_file_path: - try: - async with self._profile.transaction() as txn: - rev_list = await txn.handle.fetch(CATEGORY_REV_LIST, rev_reg_def_id) - rev_reg_info = await txn.handle.fetch( - CATEGORY_REV_REG_INFO, rev_reg_def_id, for_update=True - ) - rev_reg_def = await txn.handle.fetch( - CATEGORY_REV_REG_DEF, rev_reg_def_id - ) - rev_key = await txn.handle.fetch( - CATEGORY_REV_REG_DEF_PRIVATE, rev_reg_def_id - ) - if not rev_list: - raise AnonCredsIssuerError("Revocation registry not found") - if not rev_reg_info: - raise AnonCredsIssuerError( - "Revocation registry metadata not found" - ) - if not rev_reg_def: - raise AnonCredsIssuerError( - "Revocation registry definition not found" - ) - if not rev_key: - raise AnonCredsIssuerError( - "Revocation registry definition private data not found" - ) - # NOTE: we increment the index ahead of time to keep the - # transaction short. The revocation registry itself will NOT - # be updated because we always use ISSUANCE_BY_DEFAULT. - # If something goes wrong later, the index will be skipped. - # FIXME - double check issuance type in case of upgraded wallet? - rev_info = rev_reg_info.value_json - rev_reg_index = rev_info["curr_id"] + 1 - try: - rev_reg_def = RevocationRegistryDefinition.load( - rev_reg_def.raw_value - ) - rev_list = RevocationStatusList.load(rev_list.raw_value) - except AnoncredsError as err: - raise AnonCredsIssuerError( - "Error loading revocation registry definition" - ) from err - if rev_reg_index > rev_reg_def.max_cred_num: - raise AnonCredsIssuerRevocationRegistryFullError( - "Revocation registry is full" - ) - rev_info["curr_id"] = rev_reg_index - await txn.handle.replace( - CATEGORY_REV_REG_INFO, rev_reg_def_id, value_json=rev_info - ) - await txn.commit() - except AskarError as err: - raise AnonCredsIssuerError( - "Error updating revocation registry index" - ) from err - - revoc = CredentialRevocationConfig( - rev_reg_def, - rev_key.raw_value, - rev_reg_index, - tails_file_path, - ) - credential_revocation_id = str(rev_reg_index) - else: - revoc = None - credential_revocation_id = None - rev_list = None - try: credential = await asyncio.get_event_loop().run_in_executor( None, @@ -1001,258 +571,12 @@ async def _create_credential( credential_request, raw_values, None, - rev_reg_def_id, - rev_list, - revoc, + None, + None, + None, ), ) except AnoncredsError as err: raise AnonCredsIssuerError("Error creating credential") from err - return credential.to_json(), credential_revocation_id - - async def create_credential( - self, - credential_offer: dict, - credential_request: dict, - credential_values: dict, - *, - retries: int = 5, - ) -> Tuple[str, str, str]: - """ - Create a credential. - - Args - credential_offer: Credential Offer to create credential for - credential_request: Credential request to create credential for - credential_values: Values to go in credential - revoc_reg_id: ID of the revocation registry - retries: number of times to retry credential creation - - Returns: - A tuple of created credential and revocation id - - """ - anoncreds_registry = self.profile.inject(AnonCredsRegistry) - schema_id = credential_offer["schema_id"] - schema_result = await anoncreds_registry.get_schema(self.profile, schema_id) - cred_def_id = credential_offer["cred_def_id"] - - revocable = await self.cred_def_supports_revocation(cred_def_id) - - for attempt in range(max(retries, 1)): - if attempt > 0: - LOGGER.info( - "Waiting 2s before retrying credential issuance for cred def '%s'", - cred_def_id, - ) - await asyncio.sleep(2) - - rev_reg_def_result = None - if revocable: - rev_reg_def_result = await self.get_or_create_active_registry( - cred_def_id - ) - if ( - rev_reg_def_result.revocation_registry_definition_state.state - != STATE_FINISHED - ): - continue - rev_reg_def_id = rev_reg_def_result.rev_reg_def_id - tails_file_path = self.get_local_tails_path( - rev_reg_def_result.rev_reg_def - ) - else: - rev_reg_def_id = None - tails_file_path = None - - try: - cred_json, cred_rev_id = await self._create_credential( - cred_def_id, - schema_result.schema_value.attr_names, - credential_offer, - credential_request, - credential_values, - rev_reg_def_id, - tails_file_path, - ) - except AnonCredsIssuerRevocationRegistryFullError: - # unlucky, another instance filled the registry first - continue - - if ( - rev_reg_def_result - and rev_reg_def_result.rev_reg_def.value.max_cred_num - <= int(cred_rev_id) - ): - await self.handle_full_registry(rev_reg_def_id) - - return cred_json, cred_rev_id, rev_reg_def_id - - raise AnonCredsIssuerError( - f"Cred def '{cred_def_id}' has no active revocation registry" - ) - - async def revoke_credentials( - self, - revoc_reg_id: str, - tails_file_path: str, - cred_revoc_ids: Sequence[str], - ) -> RevokeResult: - """ - Revoke a set of credentials in a revocation registry. - - Args: - revoc_reg_id: ID of the revocation registry - tails_file_path: path to the local tails file - cred_revoc_ids: sequences of credential indexes in the revocation registry - - Returns: - Tuple with the update revocation list, list of cred rev ids not revoked - - """ - - # TODO This method should return the old list, the new list, - # and the list of changed indices - prev_list = None - updated_list = None - failed_crids = set() - max_attempt = 5 - attempt = 0 - - while True: - attempt += 1 - if attempt >= max_attempt: - raise AnonCredsIssuerError( - "Repeated conflict attempting to update registry" - ) - try: - async with self._profile.session() as session: - rev_reg_def_entry = await session.handle.fetch( - CATEGORY_REV_REG_DEF, revoc_reg_id - ) - rev_list_entry = await session.handle.fetch( - CATEGORY_REV_LIST, revoc_reg_id - ) - rev_reg_info = await session.handle.fetch( - CATEGORY_REV_REG_INFO, revoc_reg_id - ) - if not rev_reg_def_entry: - raise AnonCredsIssuerError( - "Revocation registry definition not found" - ) - if not rev_list_entry: - raise AnonCredsIssuerError("Revocation registry not found") - if not rev_reg_info: - raise AnonCredsIssuerError("Revocation registry metadata not found") - except AskarError as err: - raise AnonCredsIssuerError( - "Error retrieving revocation registry" - ) from err - - try: - rev_reg_def = RevocationRegistryDefinition.load( - rev_reg_def_entry.raw_value - ) - except AnoncredsError as err: - raise AnonCredsIssuerError( - "Error loading revocation registry definition" - ) from err - - rev_crids = set() - failed_crids = set() - max_cred_num = rev_reg_def.max_cred_num - rev_info = rev_reg_info.value_json - used_ids = set(rev_info.get("used_ids") or []) - - for rev_id in cred_revoc_ids: - rev_id = int(rev_id) - if rev_id < 1 or rev_id > max_cred_num: - LOGGER.error( - "Skipping requested credential revocation" - "on rev reg id %s, cred rev id=%s not in range", - revoc_reg_id, - rev_id, - ) - failed_crids.add(rev_id) - elif rev_id > rev_info["curr_id"]: - LOGGER.warn( - "Skipping requested credential revocation" - "on rev reg id %s, cred rev id=%s not yet issued", - revoc_reg_id, - rev_id, - ) - failed_crids.add(rev_id) - elif rev_id in used_ids: - LOGGER.warn( - "Skipping requested credential revocation" - "on rev reg id %s, cred rev id=%s already revoked", - revoc_reg_id, - rev_id, - ) - failed_crids.add(rev_id) - else: - rev_crids.add(rev_id) - - if not rev_crids: - break - - try: - prev_list = RevocationStatusList.load(rev_list_entry.raw_value) - except AnoncredsError as err: - raise AnonCredsIssuerError("Error loading revocation registry") from err - - try: - updated_list = await asyncio.get_event_loop().run_in_executor( - None, - lambda: prev_list.update( - int(time.time()), - None, # issued - list(rev_crids), # revoked - rev_reg_def, - ), - ) - except AnoncredsError as err: - raise AnonCredsIssuerError( - "Error updating revocation registry" - ) from err - - try: - async with self._profile.transaction() as txn: - rev_list_upd = await txn.handle.fetch( - CATEGORY_REV_LIST, revoc_reg_id, for_update=True - ) - rev_info_upd = await txn.handle.fetch( - CATEGORY_REV_REG_INFO, revoc_reg_id, for_update=True - ) - if not rev_list_upd or not rev_reg_info: - LOGGER.warn( - "Revocation registry missing, skipping update: {}", - revoc_reg_id, - ) - updated_list = None - break - rev_info_upd = rev_info_upd.value_json - if rev_info_upd != rev_info: - # handle concurrent update to the registry by retrying - continue - await txn.handle.replace( - CATEGORY_REV_LIST, - revoc_reg_id, - updated_list.to_json_buffer(), - ) - used_ids.update(rev_crids) - rev_info_upd["used_ids"] = sorted(used_ids) - await txn.handle.replace( - CATEGORY_REV_REG_INFO, revoc_reg_id, value_json=rev_info_upd - ) - await txn.commit() - except AskarError as err: - raise AnonCredsIssuerError("Error saving revocation registry") from err - break - - return RevokeResult( - prev=prev_list, - curr=updated_list, - failed=[str(rev_id) for rev_id in sorted(failed_crids)], - ) + return credential.to_json() diff --git a/aries_cloudagent/anoncreds/revocation.py b/aries_cloudagent/anoncreds/revocation.py new file mode 100644 index 0000000000..74727b53a0 --- /dev/null +++ b/aries_cloudagent/anoncreds/revocation.py @@ -0,0 +1,842 @@ +"""Revocation through ledger agnostic AnonCreds interface.""" + +import asyncio +import hashlib +import http +import logging +import os +from pathlib import Path +import time +from typing import List, NamedTuple, Optional, Sequence, Tuple +from urllib.parse import urlparse + +from anoncreds import ( + AnoncredsError, + Credential, + CredentialRevocationConfig, + RevocationRegistryDefinition, + RevocationStatusList, +) +from aries_askar.error import AskarError +import base58 +from requests import RequestException, Session + +from ..askar.profile import AskarProfile, AskarProfileSession +from ..core.error import BaseError +from ..core.profile import Profile +from ..tails.base import BaseTailsServer +from .issuer import ( + AnonCredsIssuer, + CATEGORY_CRED_DEF, + CATEGORY_CRED_DEF_PRIVATE, + STATE_FINISHED, +) +from .models.anoncreds_revocation import ( + RevList, + RevRegDef, + RevRegDefResult, + RevRegDefState, +) +from .registry import AnonCredsRegistry +from .util import indy_client_dir + +LOGGER = logging.getLogger(__name__) + +CATEGORY_REV_LIST = "revocation_list" +CATEGORY_REV_REG_INFO = "revocation_reg_info" +CATEGORY_REV_REG_DEF = "revocation_reg_def" +CATEGORY_REV_REG_DEF_PRIVATE = "revocation_reg_def_private" +CATEGORY_REV_REG_ISSUER = "revocation_reg_def_issuer" + + +class AnonCredsRevocationError(BaseError): + """Generic revocation error.""" + + +class AnonCredsRevocationRegistryFullError(AnonCredsRevocationError): + """Revocation registry is full when issuing a new credential.""" + + +class RevokeResult(NamedTuple): + prev: Optional[RevocationStatusList] = None + curr: Optional[RevocationStatusList] = None + failed: Optional[Sequence[str]] = None + + +class AnonCredsRevocation: + """Revocation registry operations manager.""" + + def __init__(self, profile: Profile): + """ + Initialize an AnonCredsRevocation instance. + + Args: + profile: The active profile instance + + """ + self._profile = profile + + @property + def profile(self) -> AskarProfile: + """Accessor for the profile instance.""" + if not isinstance(self._profile, AskarProfile): + raise ValueError("AnonCreds interface requires Askar") + + return self._profile + + # Revocation artifact management + + async def _finish_registration( + self, txn: AskarProfileSession, category: str, job_id: str, registered_id: str + ): + entry = await txn.handle.fetch( + category, + job_id, + for_update=True, + ) + if not entry: + raise AnonCredsRevocationError( + f"{category} with job id {job_id} could not be found" + ) + + tags = entry.tags + tags["state"] = STATE_FINISHED + await txn.handle.insert( + category, + registered_id, + value=entry.value, + tags=tags, + ) + await txn.handle.remove(category, job_id) + + async def create_and_register_revocation_registry_definition( + self, + issuer_id: str, + cred_def_id: str, + registry_type: str, + tag: str, + max_cred_num: int, + options: Optional[dict] = None, + ) -> RevRegDefResult: + """ + Create a new revocation registry and register on network. + + Args: + issuer_id (str): issuer identifier + cred_def_id (str): credential definition identifier + registry_type (str): revocation registry type + tag (str): revocation registry tag + max_cred_num (int): maximum number of credentials supported + options (dict): revocation registry options + + Returns: + RevRegDefResult: revocation registry definition result + + """ + try: + async with self.profile.session() as session: + cred_def = await session.handle.fetch(CATEGORY_CRED_DEF, cred_def_id) + except AskarError as err: + raise AnonCredsRevocationError( + "Error retrieving credential definition" + ) from err + + if not cred_def: + raise AnonCredsRevocationError( + "Credential definition not found for revocation registry" + ) + + tails_dir = indy_client_dir("tails", create=True) + + try: + ( + rev_reg_def, + rev_reg_def_private, + ) = await asyncio.get_event_loop().run_in_executor( + None, + lambda: RevocationRegistryDefinition.create( + cred_def_id, + cred_def.raw_value, + issuer_id, + tag, + registry_type, + max_cred_num, + tails_dir_path=tails_dir, + ), + ) + except AnoncredsError as err: + raise AnonCredsRevocationError( + "Error creating revocation registry" + ) from err + + rev_reg_def = RevRegDef.from_native(rev_reg_def) + + public_tails_uri = self.generate_public_tails_uri(rev_reg_def) + rev_reg_def.value.tails_location = public_tails_uri + anoncreds_registry = self.profile.inject(AnonCredsRegistry) + result = await anoncreds_registry.register_revocation_registry_definition( + self.profile, rev_reg_def, options + ) + + rev_reg_def_id = result.rev_reg_def_id + rev_reg_def_json = rev_reg_def.to_json() + + try: + async with self.profile.transaction() as txn: + await txn.handle.insert( + CATEGORY_REV_REG_INFO, + rev_reg_def_id, + value_json={"curr_id": 0, "used_ids": []}, + ) + await txn.handle.insert( + CATEGORY_REV_REG_DEF, + rev_reg_def_id, + rev_reg_def_json, + tags={"cred_def_id": cred_def_id}, + ) + await txn.handle.insert( + CATEGORY_REV_REG_DEF_PRIVATE, + rev_reg_def_id, + rev_reg_def_private.to_json_buffer(), + ) + await txn.commit() + except AskarError as err: + raise AnonCredsRevocationError( + "Error saving new revocation registry" + ) from err + + return result + + async def finish_revocation_registry_definition( + self, job_id: str, rev_reg_def_id: str + ): + """Mark a rev reg def as finished.""" + async with self.profile.transaction() as txn: + await self._finish_registration( + txn, CATEGORY_REV_REG_DEF, job_id, rev_reg_def_id + ) + await self._finish_registration( + txn, + CATEGORY_REV_REG_INFO, + job_id, + rev_reg_def_id, + ) + await self._finish_registration( + txn, + CATEGORY_REV_REG_DEF_PRIVATE, + job_id, + rev_reg_def_id, + ) + await txn.commit() + + async def get_created_revocation_registry_definitions( + self, + cred_def_id: Optional[str] = None, + state: Optional[str] = None, + ) -> Sequence[str]: + """Retrieve IDs of rev reg defs previously created.""" + async with self.profile.session() as session: + # TODO limit? scan? + rev_reg_defs = await session.handle.fetch_all( + CATEGORY_REV_REG_DEF, + { + key: value + for key, value in { + "cred_def_id": cred_def_id, + "state": state, + }.items() + if value is not None + }, + ) + # entry.name was stored as the credential_definition's ID + return [entry.name for entry in rev_reg_defs] + + async def get_created_revocation_registry_definition( + self, + rev_reg_def_id: str, + ) -> Optional[RevRegDef]: + """Retrieve rev reg def by ID from rev reg defs previously created.""" + async with self.profile.session() as session: + rev_reg_def_entry = await session.handle.fetch( + CATEGORY_REV_REG_DEF, + name=rev_reg_def_id, + ) + + if rev_reg_def_entry: + return RevRegDef.deserialize(rev_reg_def_entry.value_json) + + return None + + async def create_and_register_revocation_list( + self, rev_reg_def_id: str, options: Optional[dict] = None + ): + """Create and register a revocation list.""" + try: + async with self.profile.session() as session: + rev_reg_def_entry = await session.handle.fetch( + CATEGORY_REV_REG_DEF, rev_reg_def_id + ) + except AskarError as err: + raise AnonCredsRevocationError( + "Error retrieving credential definition" + ) from err + + if not rev_reg_def_entry: + raise AnonCredsRevocationError( + f"Revocation registry definition not found for id {rev_reg_def_id}" + ) + + rev_reg_def = RevRegDef.deserialize(rev_reg_def_entry.value_json) + # TODO This is a little rough; stored tails location will have public uri + rev_reg_def.value.tails_location = self.get_local_tails_path(rev_reg_def) + + rev_list = RevocationStatusList.create( + rev_reg_def_id, + rev_reg_def.to_native(), + rev_reg_def.issuer_id, + ) + + anoncreds_registry = self.profile.inject(AnonCredsRegistry) + result = await anoncreds_registry.register_revocation_list( + self.profile, rev_reg_def, RevList.from_native(rev_list), options + ) + + try: + async with self.profile.session() as session: + await session.handle.insert( + CATEGORY_REV_LIST, + rev_reg_def_id, + result.revocation_list_state.revocation_list.to_json(), + ) + except AskarError as err: + raise AnonCredsRevocationError( + "Error saving new revocation registry" + ) from err + + return result + + async def retrieve_tails(self, rev_reg_def: RevRegDef) -> str: + """Retrieve tails file from server.""" + LOGGER.info( + "Downloading the tails file with hash: %s", + rev_reg_def.value.tails_hash, + ) + + tails_file_path = Path(self.get_local_tails_path(rev_reg_def)) + tails_file_dir = tails_file_path.parent + if not tails_file_dir.exists(): + tails_file_dir.mkdir(parents=True) + + buffer_size = 65536 # should be multiple of 32 bytes for sha256 + file_hasher = hashlib.sha256() + with open(tails_file_path, "wb", buffer_size) as tails_file: + with Session() as req_session: + try: + resp = req_session.get( + rev_reg_def.value.tails_location, stream=True + ) + # Should this directly raise an Error? + if resp.status_code != http.HTTPStatus.OK: + LOGGER.warning( + f"Unexpected status code for tails file: {resp.status_code}" + ) + for buf in resp.iter_content(chunk_size=buffer_size): + tails_file.write(buf) + file_hasher.update(buf) + except RequestException as rx: + raise AnonCredsRevocationError(f"Error retrieving tails file: {rx}") + + download_tails_hash = base58.b58encode(file_hasher.digest()).decode("utf-8") + if download_tails_hash != rev_reg_def.value.tails_hash: + try: + os.remove(tails_file_path) + except OSError as err: + LOGGER.warning(f"Could not delete invalid tails file: {err}") + + raise AnonCredsRevocationError( + "The hash of the downloaded tails file does not match." + ) + + return str(tails_file_path) + + def _check_url(self, url) -> None: + parsed = urlparse(url) + if not (parsed.scheme and parsed.netloc and parsed.path): + raise AnonCredsRevocationError("URI {} is not a valid URL".format(url)) + + def generate_public_tails_uri(self, rev_reg_def: RevRegDef): + """Construct tails uri from rev_reg_def.""" + tails_base_url = self.profile.settings.get("tails_server_base_url") + if not tails_base_url: + raise AnonCredsRevocationError("tails_server_base_url not configured") + + public_tails_uri = ( + tails_base_url.rstrip("/") + f"/{rev_reg_def.value.tails_hash}" + ) + + self._check_url(public_tails_uri) + return public_tails_uri + + def get_local_tails_path(self, rev_reg_def: RevRegDef) -> str: + """Get the local path to the tails file.""" + tails_dir = indy_client_dir("tails", create=False) + return os.path.join(tails_dir, rev_reg_def.value.tails_hash) + + 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) + if not tails_server: + raise AnonCredsRevocationError("Tails server not configured") + if not Path(self.get_local_tails_path(rev_reg_def)).is_file(): + raise AnonCredsRevocationError("Local tails file not found") + + (upload_success, result) = await tails_server.upload_tails_file( + self.profile.context, + rev_reg_def.value.tails_hash, + self.get_local_tails_path(rev_reg_def), + interval=0.8, + backoff=-0.5, + max_attempts=5, # heuristic: respect HTTP timeout + ) + if not upload_success: + raise AnonCredsRevocationError( + f"Tails file for rev reg for {rev_reg_def.cred_def_id} " + f"failed to upload: {result}" + ) + if rev_reg_def.value.tails_location != result: + raise AnonCredsRevocationError( + f"Tails file for rev reg for {rev_reg_def.cred_def_id} " + f"uploaded to wrong location: {result} " + f"(should have been {rev_reg_def.value.tails_location})" + ) + + async def get_or_fetch_local_tails_path(self, rev_reg_def: RevRegDef) -> str: + """Return path to local tails file. + + If not present, retrieve from tails server. + """ + tails_file_path = self.get_local_tails_path(rev_reg_def) + if Path(tails_file_path).is_file(): + return tails_file_path + return await self.retrieve_tails(rev_reg_def) + + # Registry Management + + async def handle_full_registry(self, rev_reg_def_id: str): + """Update the registry status and start the next registry generation.""" + # TODO + + async def get_or_create_active_registry(self, cred_def_id: str) -> RevRegDefResult: + """Get or create a revocation registry for the given cred def id.""" + async with self.profile.session() as session: + rev_reg_defs = await session.handle.fetch_all( + CATEGORY_REV_REG_DEF, + { + "cred_def_id": cred_def_id, + }, + ) + + if not rev_reg_defs: + raise AnonCredsRevocationError("No active registry") + + # TODO Sort and return most recent + # TODO Create a registry if none available + entry = rev_reg_defs[0] + + rev_reg_def = RevRegDef.deserialize(entry.value_json) + result = RevRegDefResult( + None, + RevRegDefState( + state=STATE_FINISHED, + revocation_registry_definition_id=entry.name, + revocation_registry_definition=rev_reg_def, + ), + registration_metadata={}, + revocation_registry_definition_metadata={}, + ) + return result + + # Credential Operations + + async def _create_credential( + self, + credential_definition_id: str, + schema_attributes: List[str], + credential_offer: dict, + credential_request: dict, + credential_values: dict, + rev_reg_def_id: Optional[str] = None, + tails_file_path: Optional[str] = None, + ) -> Tuple[str, str]: + try: + async with self.profile.session() as session: + cred_def = await session.handle.fetch( + CATEGORY_CRED_DEF, credential_definition_id + ) + cred_def_private = await session.handle.fetch( + CATEGORY_CRED_DEF_PRIVATE, credential_definition_id + ) + except AskarError as err: + raise AnonCredsRevocationError( + "Error retrieving credential definition" + ) from err + if not cred_def or not cred_def_private: + raise AnonCredsRevocationError( + "Credential definition not found for credential issuance" + ) + + raw_values = {} + for attribute in schema_attributes: + # Ensure every attribute present in schema to be set. + # Extraneous attribute names are ignored. + try: + credential_value = credential_values[attribute] + except KeyError: + raise AnonCredsRevocationError( + "Provided credential values are missing a value " + f"for the schema attribute '{attribute}'" + ) + + raw_values[attribute] = str(credential_value) + + if rev_reg_def_id and tails_file_path: + try: + async with self.profile.transaction() as txn: + rev_list = await txn.handle.fetch(CATEGORY_REV_LIST, rev_reg_def_id) + rev_reg_info = await txn.handle.fetch( + CATEGORY_REV_REG_INFO, rev_reg_def_id, for_update=True + ) + rev_reg_def = await txn.handle.fetch( + CATEGORY_REV_REG_DEF, rev_reg_def_id + ) + rev_key = await txn.handle.fetch( + CATEGORY_REV_REG_DEF_PRIVATE, rev_reg_def_id + ) + if not rev_list: + raise AnonCredsRevocationError("Revocation registry not found") + if not rev_reg_info: + raise AnonCredsRevocationError( + "Revocation registry metadata not found" + ) + if not rev_reg_def: + raise AnonCredsRevocationError( + "Revocation registry definition not found" + ) + if not rev_key: + raise AnonCredsRevocationError( + "Revocation registry definition private data not found" + ) + # NOTE: we increment the index ahead of time to keep the + # transaction short. The revocation registry itself will NOT + # be updated because we always use ISSUANCE_BY_DEFAULT. + # If something goes wrong later, the index will be skipped. + # FIXME - double check issuance type in case of upgraded wallet? + rev_info = rev_reg_info.value_json + rev_reg_index = rev_info["curr_id"] + 1 + try: + rev_reg_def = RevocationRegistryDefinition.load( + rev_reg_def.raw_value + ) + rev_list = RevocationStatusList.load(rev_list.raw_value) + except AnoncredsError as err: + raise AnonCredsRevocationError( + "Error loading revocation registry definition" + ) from err + if rev_reg_index > rev_reg_def.max_cred_num: + raise AnonCredsRevocationRegistryFullError( + "Revocation registry is full" + ) + rev_info["curr_id"] = rev_reg_index + await txn.handle.replace( + CATEGORY_REV_REG_INFO, rev_reg_def_id, value_json=rev_info + ) + await txn.commit() + except AskarError as err: + raise AnonCredsRevocationError( + "Error updating revocation registry index" + ) from err + + revoc = CredentialRevocationConfig( + rev_reg_def, + rev_key.raw_value, + rev_reg_index, + tails_file_path, + ) + credential_revocation_id = str(rev_reg_index) + else: + revoc = None + credential_revocation_id = None + rev_list = None + + try: + credential = await asyncio.get_event_loop().run_in_executor( + None, + lambda: Credential.create( + cred_def.raw_value, + cred_def_private.raw_value, + credential_offer, + credential_request, + raw_values, + None, + rev_reg_def_id, + rev_list, + revoc, + ), + ) + except AnoncredsError as err: + raise AnonCredsRevocationError("Error creating credential") from err + + return credential.to_json(), credential_revocation_id + + async def create_credential( + self, + credential_offer: dict, + credential_request: dict, + credential_values: dict, + *, + retries: int = 5, + ) -> Tuple[str, str, str]: + """ + Create a credential. + + Args + credential_offer: Credential Offer to create credential for + credential_request: Credential request to create credential for + credential_values: Values to go in credential + revoc_reg_id: ID of the revocation registry + retries: number of times to retry credential creation + + Returns: + A tuple of created credential and revocation id + + """ + issuer = AnonCredsIssuer(self.profile) + anoncreds_registry = self.profile.inject(AnonCredsRegistry) + schema_id = credential_offer["schema_id"] + schema_result = await anoncreds_registry.get_schema(self.profile, schema_id) + cred_def_id = credential_offer["cred_def_id"] + + revocable = await issuer.cred_def_supports_revocation(cred_def_id) + + for attempt in range(max(retries, 1)): + if attempt > 0: + LOGGER.info( + "Waiting 2s before retrying credential issuance for cred def '%s'", + cred_def_id, + ) + await asyncio.sleep(2) + + rev_reg_def_result = None + if revocable: + rev_reg_def_result = await self.get_or_create_active_registry( + cred_def_id + ) + if ( + rev_reg_def_result.revocation_registry_definition_state.state + != STATE_FINISHED + ): + continue + rev_reg_def_id = rev_reg_def_result.rev_reg_def_id + tails_file_path = self.get_local_tails_path( + rev_reg_def_result.rev_reg_def + ) + else: + rev_reg_def_id = None + tails_file_path = None + + try: + cred_json, cred_rev_id = await self._create_credential( + cred_def_id, + schema_result.schema_value.attr_names, + credential_offer, + credential_request, + credential_values, + rev_reg_def_id, + tails_file_path, + ) + except AnonCredsRevocationRegistryFullError: + # unlucky, another instance filled the registry first + continue + + if ( + rev_reg_def_result + and rev_reg_def_result.rev_reg_def.value.max_cred_num + <= int(cred_rev_id) + ): + await self.handle_full_registry(rev_reg_def_id) + + return cred_json, cred_rev_id, rev_reg_def_id + + raise AnonCredsRevocationError( + f"Cred def '{cred_def_id}' has no active revocation registry" + ) + + async def revoke_credentials( + self, + revoc_reg_id: str, + tails_file_path: str, + cred_revoc_ids: Sequence[str], + ) -> RevokeResult: + """ + Revoke a set of credentials in a revocation registry. + + Args: + revoc_reg_id: ID of the revocation registry + tails_file_path: path to the local tails file + cred_revoc_ids: sequences of credential indexes in the revocation registry + + Returns: + Tuple with the update revocation list, list of cred rev ids not revoked + + """ + + # TODO This method should return the old list, the new list, + # and the list of changed indices + prev_list = None + updated_list = None + failed_crids = set() + max_attempt = 5 + attempt = 0 + + while True: + attempt += 1 + if attempt >= max_attempt: + raise AnonCredsRevocationError( + "Repeated conflict attempting to update registry" + ) + try: + async with self.profile.session() as session: + rev_reg_def_entry = await session.handle.fetch( + CATEGORY_REV_REG_DEF, revoc_reg_id + ) + rev_list_entry = await session.handle.fetch( + CATEGORY_REV_LIST, revoc_reg_id + ) + rev_reg_info = await session.handle.fetch( + CATEGORY_REV_REG_INFO, revoc_reg_id + ) + if not rev_reg_def_entry: + raise AnonCredsRevocationError( + "Revocation registry definition not found" + ) + if not rev_list_entry: + raise AnonCredsRevocationError("Revocation registry not found") + if not rev_reg_info: + raise AnonCredsRevocationError( + "Revocation registry metadata not found" + ) + except AskarError as err: + raise AnonCredsRevocationError( + "Error retrieving revocation registry" + ) from err + + try: + rev_reg_def = RevocationRegistryDefinition.load( + rev_reg_def_entry.raw_value + ) + except AnoncredsError as err: + raise AnonCredsRevocationError( + "Error loading revocation registry definition" + ) from err + + rev_crids = set() + failed_crids = set() + max_cred_num = rev_reg_def.max_cred_num + rev_info = rev_reg_info.value_json + used_ids = set(rev_info.get("used_ids") or []) + + for rev_id in cred_revoc_ids: + rev_id = int(rev_id) + if rev_id < 1 or rev_id > max_cred_num: + LOGGER.error( + "Skipping requested credential revocation" + "on rev reg id %s, cred rev id=%s not in range", + revoc_reg_id, + rev_id, + ) + failed_crids.add(rev_id) + elif rev_id > rev_info["curr_id"]: + LOGGER.warn( + "Skipping requested credential revocation" + "on rev reg id %s, cred rev id=%s not yet issued", + revoc_reg_id, + rev_id, + ) + failed_crids.add(rev_id) + elif rev_id in used_ids: + LOGGER.warn( + "Skipping requested credential revocation" + "on rev reg id %s, cred rev id=%s already revoked", + revoc_reg_id, + rev_id, + ) + failed_crids.add(rev_id) + else: + rev_crids.add(rev_id) + + if not rev_crids: + break + + try: + prev_list = RevocationStatusList.load(rev_list_entry.raw_value) + except AnoncredsError as err: + raise AnonCredsRevocationError( + "Error loading revocation registry" + ) from err + + try: + updated_list = await asyncio.get_event_loop().run_in_executor( + None, + lambda: prev_list.update( + int(time.time()), + None, # issued + list(rev_crids), # revoked + rev_reg_def, + ), + ) + except AnoncredsError as err: + raise AnonCredsRevocationError( + "Error updating revocation registry" + ) from err + + try: + async with self.profile.transaction() as txn: + rev_list_upd = await txn.handle.fetch( + CATEGORY_REV_LIST, revoc_reg_id, for_update=True + ) + rev_info_upd = await txn.handle.fetch( + CATEGORY_REV_REG_INFO, revoc_reg_id, for_update=True + ) + if not rev_list_upd or not rev_reg_info: + LOGGER.warn( + "Revocation registry missing, skipping update: {}", + revoc_reg_id, + ) + updated_list = None + break + rev_info_upd = rev_info_upd.value_json + if rev_info_upd != rev_info: + # handle concurrent update to the registry by retrying + continue + await txn.handle.replace( + CATEGORY_REV_LIST, + revoc_reg_id, + updated_list.to_json_buffer(), + ) + used_ids.update(rev_crids) + rev_info_upd["used_ids"] = sorted(used_ids) + await txn.handle.replace( + CATEGORY_REV_REG_INFO, revoc_reg_id, value_json=rev_info_upd + ) + await txn.commit() + except AskarError as err: + raise AnonCredsRevocationError( + "Error saving revocation registry" + ) from err + break + + return RevokeResult( + prev=prev_list, + curr=updated_list, + failed=[str(rev_id) for rev_id in sorted(failed_crids)], + ) diff --git a/aries_cloudagent/anoncreds/routes.py b/aries_cloudagent/anoncreds/routes.py index d42f92da9b..5c5dca0ca5 100644 --- a/aries_cloudagent/anoncreds/routes.py +++ b/aries_cloudagent/anoncreds/routes.py @@ -11,30 +11,24 @@ response_schema, ) from marshmallow import fields -from aries_cloudagent.askar.profile import AskarProfile - -from aries_cloudagent.revocation.routes import ( - RevRegIdMatchInfoSchema, - RevocationModuleResponseSchema, -) from ..admin.request_context import AdminRequestContext +from ..askar.profile import AskarProfile from ..messaging.models.openapi import OpenAPISchema from ..messaging.valid import UUIDFour from ..revocation.error import RevocationNotSupportedError +from ..revocation.routes import RevRegIdMatchInfoSchema, RevocationModuleResponseSchema from ..storage.error import StorageNotFoundError from .issuer import AnonCredsIssuer, AnonCredsIssuerError from .models.anoncreds_cred_def import CredDefResultSchema, GetCredDefResultSchema -from .models.anoncreds_revocation import ( - RevRegDefResultSchema, - RevListResultSchema, -) +from .models.anoncreds_revocation import RevListResultSchema, RevRegDefResultSchema from .models.anoncreds_schema import ( AnonCredsSchemaSchema, GetSchemaResultSchema, SchemaResultSchema, ) from .registry import AnonCredsRegistry +from .revocation import AnonCredsRevocation, AnonCredsRevocationError LOGGER = logging.getLogger(__name__) @@ -342,24 +336,7 @@ class RevRegCreateRequestSchema(OpenAPISchema): @request_schema(RevRegCreateRequestSchema()) @response_schema(RevRegDefResultSchema(), 200, description="") async def rev_reg_def_post(request: web.BaseRequest): - """Request handler for creating . - - Args: - - - (method) def create_and_register_revocation_registry_definition( - issuer_id: str, - cred_def_id: str, - tag: str, - max_cred_num: int, - registry_type: str, - tails_base_path: str, - options: dict[Unknown, Unknown] | None = None - ) -> Coroutine[Any, Any, RevRegDefResult] - - Returns: - - """ + """Request handler for creating revocation registry definition.""" context: AdminRequestContext = request["context"] body = await request.json() issuer_id = body.get("issuerId") @@ -368,6 +345,7 @@ async def rev_reg_def_post(request: web.BaseRequest): options = body.get("options") issuer = AnonCredsIssuer(context.profile) + revocation = AnonCredsRevocation(context.profile) # check we published this cred def found = await issuer.match_created_credential_definitions(cred_def_id) if not found: @@ -377,7 +355,7 @@ async def rev_reg_def_post(request: web.BaseRequest): try: result = await shield( - issuer.create_and_register_revocation_registry_definition( + revocation.create_and_register_revocation_registry_definition( issuer_id, cred_def_id, registry_type="CL_ACCUM", @@ -388,6 +366,8 @@ async def rev_reg_def_post(request: web.BaseRequest): ) except RevocationNotSupportedError as e: raise web.HTTPBadRequest(reason=e.message) from e + except AnonCredsRevocationError as e: + raise web.HTTPBadRequest(reason=e.message) from e return web.json_response(result.serialize()) @@ -405,33 +385,16 @@ class RevListCreateRequestSchema(OpenAPISchema): @request_schema(RevListCreateRequestSchema()) @response_schema(RevListResultSchema(), 200, description="") async def rev_list_post(request: web.BaseRequest): - """Request handler for creating . - - Args: - - - (method) def create_and_register_revocation_registry_definition( - issuer_id: str, - cred_def_id: str, - tag: str, - max_cred_num: int, - registry_type: str, - tails_base_path: str, - options: dict[Unknown, Unknown] | None = None - ) -> Coroutine[Any, Any, RevRegDefResult] - - Returns: - - """ + """Request handler for creating registering a revocation list.""" context: AdminRequestContext = request["context"] body = await request.json() rev_reg_def_id = body.get("revRegDefId") options = body.get("options") - issuer = AnonCredsIssuer(context.profile) + revocation = AnonCredsRevocation(context.profile) try: result = await shield( - issuer.create_and_register_revocation_list( + revocation.create_and_register_revocation_list( rev_reg_def_id, options, ) @@ -440,7 +403,7 @@ async def rev_list_post(request: web.BaseRequest): except StorageNotFoundError as err: raise web.HTTPNotFound(reason=err.roll_up) from err - except AnonCredsIssuerError as err: + except AnonCredsRevocationError as err: raise web.HTTPBadRequest(reason=err.roll_up) from err return web.json_response(result.serialize()) @@ -464,14 +427,14 @@ async def upload_tails_file(request: web.BaseRequest): profile: AskarProfile = context.profile rev_reg_id = request.match_info["rev_reg_id"] try: - issuer = AnonCredsIssuer(profile) - rev_reg_def = await issuer.get_created_revocation_registry_definition( + revocation = AnonCredsRevocation(profile) + rev_reg_def = await revocation.get_created_revocation_registry_definition( rev_reg_id ) if rev_reg_def is None: raise web.HTTPNotFound(reason="No rev reg def found") - await issuer.upload_tails_file(rev_reg_def) + await revocation.upload_tails_file(rev_reg_def) except AnonCredsIssuerError as e: raise web.HTTPInternalServerError(reason=str(e)) from e diff --git a/aries_cloudagent/protocols/issue_credential/v1_0/manager.py b/aries_cloudagent/protocols/issue_credential/v1_0/manager.py index d6504cc664..d7ac26222b 100644 --- a/aries_cloudagent/protocols/issue_credential/v1_0/manager.py +++ b/aries_cloudagent/protocols/issue_credential/v1_0/manager.py @@ -3,35 +3,33 @@ import asyncio import json import logging - from typing import Mapping, Optional, Tuple +from ....anoncreds.holder import AnonCredsHolder, AnonCredsHolderError +from ....anoncreds.issuer import AnonCredsIssuer +from ....anoncreds.revocation import ( + AnonCredsRevocation, + AnonCredsRevocationRegistryFullError, +) from ....cache.base import BaseCache from ....connections.models.conn_record import ConnRecord from ....core.error import BaseError from ....core.profile import Profile -from ....anoncreds.holder import AnonCredsHolder, AnonCredsHolderError -from ....anoncreds.issuer import ( - AnonCredsIssuer, - AnonCredsIssuerRevocationRegistryFullError, -) from ....ledger.multiple_ledger.ledger_requests_executor import ( GET_CRED_DEF, GET_SCHEMA, IndyLedgerRequestsExecutor, ) from ....messaging.credential_definitions.util import ( - CRED_DEF_TAGS, CRED_DEF_SENT_RECORD_TYPE, + CRED_DEF_TAGS, ) from ....messaging.responder import BaseResponder from ....multitenant.base import BaseMultitenantManager -from ....revocation.anoncreds import AnonCredsRevocation from ....revocation.models.issuer_cred_rev_record import IssuerCredRevRecord from ....revocation.models.revocation_registry import RevocationRegistry from ....storage.base import BaseStorage from ....storage.error import StorageError, StorageNotFoundError - from ...out_of_band.v1_0.models.oob_record import OobRecord from .messages.credential_ack import CredentialAck from .messages.credential_issue import CredentialIssue @@ -43,9 +41,7 @@ from .messages.credential_proposal import CredentialProposal from .messages.credential_request import CredentialRequest from .messages.inner.credential_preview import CredentialPreview -from .models.credential_exchange import ( - V10CredentialExchange, -) +from .models.credential_exchange import V10CredentialExchange LOGGER = logging.getLogger(__name__) @@ -665,7 +661,7 @@ async def issue_credential( rev_reg_id, tails_path, ) - except AnonCredsIssuerRevocationRegistryFullError: + except AnonCredsRevocationRegistryFullError: # unlucky, another instance filled the registry first continue diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/handler.py b/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/handler.py index 155a0d7d17..ff50486030 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/handler.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/handler.py @@ -6,6 +6,8 @@ from marshmallow import RAISE +from ......anoncreds.revocation import AnonCredsRevocation + from ......anoncreds.registry import AnonCredsRegistry from ......anoncreds.holder import AnonCredsHolder, AnonCredsHolderError from ......anoncreds.issuer import ( @@ -325,10 +327,19 @@ async def issue_credential( ) issuer = AnonCredsIssuer(self.profile) + cred_def_id = cred_offer["cred_def_id"] + if await issuer.cred_def_supports_revocation(cred_def_id): + revocation = AnonCredsRevocation(self.profile) + cred_json, cred_rev_id, rev_reg_def_id = await revocation.create_credential( + cred_offer, cred_request, cred_values + ) + else: + cred_json = await issuer.create_credential( + cred_offer, cred_request, cred_values + ) + cred_rev_id = None + rev_reg_def_id = None - cred_json, cred_rev_id, rev_reg_def_id = await issuer.create_credential( - cred_offer, cred_request, cred_values - ) result = self.get_format_data(CRED_20_ISSUE, json.loads(cred_json)) async with self._profile.transaction() as txn: @@ -392,8 +403,8 @@ async def store_credential( mime_types = cred_offer_message.credential_preview.mime_types() or None if rev_reg_def: - issuer = AnonCredsIssuer(self.profile) - await issuer.get_or_fetch_local_tails_path(rev_reg_def) + revocation = AnonCredsRevocation(self.profile) + await revocation.get_or_fetch_local_tails_path(rev_reg_def) try: detail_record = await self.get_detail_record(cred_ex_record.cred_ex_id) if detail_record is None: diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/tests/test_handler.py b/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/tests/test_handler.py index 80a98d2c71..377060b84c 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/tests/test_handler.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/formats/indy/tests/test_handler.py @@ -1,49 +1,46 @@ import asyncio from copy import deepcopy -from time import time import json +from time import time + from asynctest import TestCase as AsyncTestCase from asynctest import mock as async_mock from marshmallow import ValidationError from more_itertools import side_effect from .. import handler as test_module - +from .......anoncreds.holder import AnonCredsHolder +from .......anoncreds.issuer import AnonCredsIssuer +from .......anoncreds.revocation import AnonCredsRevocationRegistryFullError +from .......cache.base import BaseCache +from .......cache.in_memory import InMemoryCache from .......core.in_memory import InMemoryProfile from .......ledger.base import BaseLedger from .......ledger.multiple_ledger.ledger_requests_executor import ( IndyLedgerRequestsExecutor, ) +from .......messaging.credential_definitions.util import CRED_DEF_SENT_RECORD_TYPE +from .......messaging.decorators.attach_decorator import AttachDecorator from .......multitenant.base import BaseMultitenantManager from .......multitenant.manager import MultitenantManager -from .......anoncreds.issuer import AnonCredsIssuer -from .......cache.in_memory import InMemoryCache -from .......cache.base import BaseCache -from .......storage.record import StorageRecord from .......storage.error import StorageNotFoundError -from .......messaging.credential_definitions.util import CRED_DEF_SENT_RECORD_TYPE -from .......messaging.decorators.attach_decorator import AttachDecorator -from .......anoncreds.holder import AnonCredsHolder -from ....models.detail.indy import V20CredExRecordIndy -from ....messages.cred_proposal import V20CredProposal -from ....messages.cred_format import V20CredFormat -from ....messages.cred_issue import V20CredIssue -from ....messages.inner.cred_preview import V20CredPreview, V20CredAttrSpec -from ....messages.cred_offer import V20CredOffer -from ....messages.cred_request import ( - V20CredRequest, -) -from ....models.cred_ex_record import V20CredExRecord +from .......storage.record import StorageRecord from ....message_types import ( ATTACHMENT_FORMAT, - CRED_20_PROPOSAL, + CRED_20_ISSUE, CRED_20_OFFER, + CRED_20_PROPOSAL, CRED_20_REQUEST, - CRED_20_ISSUE, ) - +from ....messages.cred_format import V20CredFormat +from ....messages.cred_issue import V20CredIssue +from ....messages.cred_offer import V20CredOffer +from ....messages.cred_proposal import V20CredProposal +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 ...handler import LOGGER, V20CredFormatError - from ..handler import IndyCredFormatHandler from ..handler import LOGGER as INDY_LOGGER @@ -1044,7 +1041,7 @@ async def test_issue_credential_rr_full(self): ) self.issuer.create_credential = async_mock.CoroutineMock( - side_effect=test_module.AnonCredsIssuerRevocationRegistryFullError("Nope") + side_effect=AnonCredsRevocationRegistryFullError("Nope") ) with async_mock.patch.object( test_module, "IndyRevocation", autospec=True diff --git a/aries_cloudagent/protocols/present_proof/indy/pres_exch_handler.py b/aries_cloudagent/protocols/present_proof/indy/pres_exch_handler.py index 28318cff4d..572583ef40 100644 --- a/aries_cloudagent/protocols/present_proof/indy/pres_exch_handler.py +++ b/aries_cloudagent/protocols/present_proof/indy/pres_exch_handler.py @@ -4,14 +4,13 @@ import time from typing import Dict, Tuple, Union -from aries_cloudagent.anoncreds.issuer import AnonCredsIssuer -from aries_cloudagent.anoncreds.models.anoncreds_cred_def import CredDef - 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.xform import indy_proof_req2non_revoc_intervals from ....anoncreds.registry import AnonCredsRegistry +from ....anoncreds.revocation import AnonCredsRevocation from ....core.error import BaseError from ....core.profile import Profile from ..v1_0.models.presentation_exchange import V10PresentationExchange @@ -193,9 +192,10 @@ async def _get_revocation_states( if rev_reg_id not in revocation_states: revocation_states[rev_reg_id] = {} rev_reg_def = revocation_registries[rev_reg_id] - # TODO Not an issuer specific operation to fetch tails - issuer = AnonCredsIssuer(self._profile) - tails_local_path = await issuer.get_or_fetch_local_tails_path(rev_reg_def) + revocation = AnonCredsRevocation(self._profile) + tails_local_path = await revocation.get_or_fetch_local_tails_path( + rev_reg_def + ) try: revocation_states[rev_reg_id][timestamp] = json.loads( await self.holder.create_revocation_state( diff --git a/aries_cloudagent/revocation/models/issuer_rev_reg_record.py b/aries_cloudagent/revocation/models/issuer_rev_reg_record.py index 0951355509..61d75734c8 100644 --- a/aries_cloudagent/revocation/models/issuer_rev_reg_record.py +++ b/aries_cloudagent/revocation/models/issuer_rev_reg_record.py @@ -14,13 +14,14 @@ from ...anoncreds.issuer import AnonCredsIssuer, AnonCredsIssuerError from ...anoncreds.models.anoncreds_revocation import ( - RevRegDef, - RevRegDefSchema, RevList, RevListResult, RevListSchema, + RevRegDef, + RevRegDefSchema, ) from ...anoncreds.registry import AnonCredsRegistry +from ...anoncreds.revocation import AnonCredsRevocation from ...anoncreds.util import indy_client_dir from ...core.profile import Profile, ProfileSession from ...ledger.base import BaseLedger @@ -177,18 +178,20 @@ async def create_and_register_def(self, profile: Profile): ) ) - issuer = AnonCredsIssuer(profile) + revocation = AnonCredsRevocation(profile) LOGGER.debug("Creating revocation registry with size: %d", self.max_cred_num) try: - result = await issuer.create_and_register_revocation_registry_definition( - self.issuer_id, - self.cred_def_id, - self.revoc_def_type, - self.tag, - self.max_cred_num, - self.options, + result = ( + await revocation.create_and_register_revocation_registry_definition( + self.issuer_id, + self.cred_def_id, + self.revoc_def_type, + self.tag, + self.max_cred_num, + self.options, + ) ) except AnonCredsIssuerError as err: raise RevocationError() from err @@ -198,7 +201,7 @@ async def create_and_register_def(self, profile: Profile): self.state = IssuerRevRegRecord.STATE_POSTED self.tails_hash = result.rev_reg_def.value.tails_hash self.tails_public_uri = result.rev_reg_def.value.tails_location - self.tails_local_path = issuer.get_local_tails_path(result.rev_reg_def) + self.tails_local_path = revocation.get_local_tails_path(result.rev_reg_def) async with profile.session() as session: await self.save(session, reason="Generated registry") @@ -371,8 +374,8 @@ async def create_and_register_list( ) ) - issuer = AnonCredsIssuer(profile) - result = await issuer.create_and_register_revocation_list( + revocation = AnonCredsRevocation(profile) + result = await revocation.create_and_register_revocation_list( self.revoc_reg_id, options, ) From ff62b918049f97a3b9aae0165b02ed33110d9482 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Tue, 30 May 2023 15:09:35 -0600 Subject: [PATCH 142/150] feat: update to use tails server image with backwards compatible put, get file methods Signed-off-by: Char Howland --- aries_cloudagent/anoncreds/revocation.py | 2 +- aries_cloudagent/revocation/routes.py | 4 +++- aries_cloudagent/tails/anoncreds_tails_server.py | 2 +- docker-compose.yml | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/aries_cloudagent/anoncreds/revocation.py b/aries_cloudagent/anoncreds/revocation.py index 74727b53a0..b45d09232a 100644 --- a/aries_cloudagent/anoncreds/revocation.py +++ b/aries_cloudagent/anoncreds/revocation.py @@ -371,7 +371,7 @@ def generate_public_tails_uri(self, rev_reg_def: RevRegDef): raise AnonCredsRevocationError("tails_server_base_url not configured") public_tails_uri = ( - tails_base_url.rstrip("/") + f"/{rev_reg_def.value.tails_hash}" + tails_base_url.rstrip("/") + f"/hash/{rev_reg_def.value.tails_hash}" ) self._check_url(public_tails_uri) diff --git a/aries_cloudagent/revocation/routes.py b/aries_cloudagent/revocation/routes.py index 4155919638..9ac67d782f 100644 --- a/aries_cloudagent/revocation/routes.py +++ b/aries_cloudagent/revocation/routes.py @@ -1319,7 +1319,9 @@ async def on_revocation_registry_init_event(profile: Profile, event: Event): # Generate the registry and upload the tails file async def generate(rr_record: IssuerRevRegRecord) -> dict: await rr_record.generate_registry(profile) - public_uri = tails_base_url.rstrip("/") + f"/{registry_record.revoc_reg_id}" + public_uri = ( + tails_base_url.rstrip("/") + f"/{registry_record.revoc_reg_id}" + ) # TODO: update to include /hash await rr_record.set_tails_file_public_uri(profile, public_uri) rev_reg_resp = await rr_record.send_def( profile, diff --git a/aries_cloudagent/tails/anoncreds_tails_server.py b/aries_cloudagent/tails/anoncreds_tails_server.py index bb175dbfc9..2447ba6e7a 100644 --- a/aries_cloudagent/tails/anoncreds_tails_server.py +++ b/aries_cloudagent/tails/anoncreds_tails_server.py @@ -47,7 +47,7 @@ async def upload_tails_file( "tails_server_upload_url setting is not set" ) - upload_url = tails_server_upload_url.rstrip("/") + f"/{filename}" + upload_url = tails_server_upload_url.rstrip("/") + f"/hash/{filename}" try: await put_file( diff --git a/docker-compose.yml b/docker-compose.yml index 5f1423ef2b..9037211eb1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -61,7 +61,7 @@ services: tails: platform: linux/amd64 - image: ghcr.io/indicio-tech/tails-server:sha-d45f581 + image: ghcr.io/indicio-tech/tails-server:sha-3d2feb2 ports: - 6543:6543 environment: From 958cc7b4172f1172c682c5f132a76b7d95494d7c Mon Sep 17 00:00:00 2001 From: Adam Burdett Date: Wed, 31 May 2023 12:50:11 -0600 Subject: [PATCH 143/150] feat(revocation): update revocation to track pending state Signed-off-by: Adam Burdett --- aries_cloudagent/anoncreds/issuer.py | 2 + aries_cloudagent/anoncreds/revocation.py | 57 ++++++++++++++++++++++++ aries_cloudagent/indy/credx/issuer.py | 8 +++- 3 files changed, 66 insertions(+), 1 deletion(-) diff --git a/aries_cloudagent/anoncreds/issuer.py b/aries_cloudagent/anoncreds/issuer.py index 968e5ac905..6d46594d13 100644 --- a/aries_cloudagent/anoncreds/issuer.py +++ b/aries_cloudagent/anoncreds/issuer.py @@ -31,6 +31,8 @@ CATEGORY_CRED_DEF_PRIVATE = "credential_def_private" CATEGORY_CRED_DEF_KEY_PROOF = "credential_def_key_proof" STATE_FINISHED = "finished" +STATE_REVOCATION_POSTED = "posted" +STATE_REVOCATION_PENDING = "pending" EVENT_PREFIX = "acapy::anoncreds::" EVENT_SCHEMA = EVENT_PREFIX + CATEGORY_SCHEMA diff --git a/aries_cloudagent/anoncreds/revocation.py b/aries_cloudagent/anoncreds/revocation.py index b45d09232a..3a79390fe1 100644 --- a/aries_cloudagent/anoncreds/revocation.py +++ b/aries_cloudagent/anoncreds/revocation.py @@ -21,11 +21,14 @@ import base58 from requests import RequestException, Session + from ..askar.profile import AskarProfile, AskarProfileSession from ..core.error import BaseError from ..core.profile import Profile from ..tails.base import BaseTailsServer from .issuer import ( + STATE_REVOCATION_PENDING, + STATE_REVOCATION_POSTED, AnonCredsIssuer, CATEGORY_CRED_DEF, CATEGORY_CRED_DEF_PRIVATE, @@ -47,6 +50,7 @@ CATEGORY_REV_REG_DEF = "revocation_reg_def" CATEGORY_REV_REG_DEF_PRIVATE = "revocation_reg_def_private" CATEGORY_REV_REG_ISSUER = "revocation_reg_def_issuer" +REV_REG_DEF_STATE_ACTIVE = "active" class AnonCredsRevocationError(BaseError): @@ -86,6 +90,29 @@ def profile(self) -> AskarProfile: # Revocation artifact management + async def _update_rev_list_record( + self, txn: AskarProfileSession, state, rev_reg_def_id: str + ): + entry = await txn.handle.fetch( + CATEGORY_REV_LIST, + rev_reg_def_id, + for_update=True, + ) + if not entry: + raise AnonCredsRevocationError( + f"{CATEGORY_REV_LIST} with rev_reg_def_id " + f"{rev_reg_def_id} could not be found" + ) + + tags = entry.tags + tags["state"] = state + await txn.handle.insert( + CATEGORY_REV_LIST, + rev_reg_def_id, + value=entry.value, + tags=tags, + ) + async def _finish_registration( self, txn: AskarProfileSession, category: str, job_id: str, registered_id: str ): @@ -109,6 +136,36 @@ async def _finish_registration( ) await txn.handle.remove(category, job_id) + async def mark_pending_revocation(self, txn: AskarProfileSession, rev_reg_id): + await self._update_rev_list_record(txn, STATE_REVOCATION_PENDING, rev_reg_id) + + async def clear_pending_revocation(self, txn: AskarProfileSession, rev_reg_id): + await self._update_rev_list_record(txn, STATE_REVOCATION_POSTED, rev_reg_id) + + async def set_active_registry( + self, txn: AskarProfileSession, state, revoc_reg_id: str + ): + entry = await txn.handle.fetch( + CATEGORY_REV_REG_DEF, + revoc_reg_id, + for_update=True, + ) + if not entry: + raise AnonCredsRevocationError( + f"{CATEGORY_REV_REG_DEF} with revoc_reg_id " + f"{revoc_reg_id} could not be found" + ) + + tags = entry.tags + tags["state"] = REV_REG_DEF_STATE_ACTIVE + tags["active"] = True + await txn.handle.insert( + CATEGORY_REV_REG_DEF, + revoc_reg_id, + value=entry.value, + tags=tags, + ) + async def create_and_register_revocation_registry_definition( self, issuer_id: str, diff --git a/aries_cloudagent/indy/credx/issuer.py b/aries_cloudagent/indy/credx/issuer.py index b4a4524948..47c9a6c0e6 100644 --- a/aries_cloudagent/indy/credx/issuer.py +++ b/aries_cloudagent/indy/credx/issuer.py @@ -19,6 +19,8 @@ Schema, ) +from aries_cloudagent.anoncreds.revocation import REV_REG_DEF_STATE_ACTIVE + from ...askar.profile import AskarProfile from ..issuer import ( @@ -592,6 +594,7 @@ async def create_and_store_revocation_registry( rev_reg_def_id = rev_reg_def.id rev_reg_def_json = rev_reg_def.to_json() + rev_reg_def_json["state"] = REV_REG_DEF_STATE_ACTIVE rev_reg_json = rev_reg.to_json() try: @@ -603,7 +606,10 @@ async def create_and_store_revocation_registry( value_json={"curr_id": 0, "used_ids": []}, ) await txn.handle.insert( - CATEGORY_REV_REG_DEF, rev_reg_def_id, rev_reg_def_json + CATEGORY_REV_REG_DEF, + rev_reg_def_id, + rev_reg_def_json, + {"active": True}, ) await txn.handle.insert( CATEGORY_REV_REG_DEF_PRIVATE, From 5aebbd873e59dfbf3ecca4f0885be41c98e244fd Mon Sep 17 00:00:00 2001 From: Adam Burdett Date: Wed, 31 May 2023 15:45:12 -0600 Subject: [PATCH 144/150] fix(revocation): pr feed back changes Signed-off-by: Adam Burdett --- aries_cloudagent/anoncreds/issuer.py | 2 -- aries_cloudagent/anoncreds/revocation.py | 5 ++--- aries_cloudagent/indy/credx/issuer.py | 4 ---- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/aries_cloudagent/anoncreds/issuer.py b/aries_cloudagent/anoncreds/issuer.py index 6d46594d13..968e5ac905 100644 --- a/aries_cloudagent/anoncreds/issuer.py +++ b/aries_cloudagent/anoncreds/issuer.py @@ -31,8 +31,6 @@ CATEGORY_CRED_DEF_PRIVATE = "credential_def_private" CATEGORY_CRED_DEF_KEY_PROOF = "credential_def_key_proof" STATE_FINISHED = "finished" -STATE_REVOCATION_POSTED = "posted" -STATE_REVOCATION_PENDING = "pending" EVENT_PREFIX = "acapy::anoncreds::" EVENT_SCHEMA = EVENT_PREFIX + CATEGORY_SCHEMA diff --git a/aries_cloudagent/anoncreds/revocation.py b/aries_cloudagent/anoncreds/revocation.py index 3a79390fe1..f7a8b0bc5b 100644 --- a/aries_cloudagent/anoncreds/revocation.py +++ b/aries_cloudagent/anoncreds/revocation.py @@ -27,8 +27,6 @@ from ..core.profile import Profile from ..tails.base import BaseTailsServer from .issuer import ( - STATE_REVOCATION_PENDING, - STATE_REVOCATION_POSTED, AnonCredsIssuer, CATEGORY_CRED_DEF, CATEGORY_CRED_DEF_PRIVATE, @@ -50,6 +48,8 @@ CATEGORY_REV_REG_DEF = "revocation_reg_def" CATEGORY_REV_REG_DEF_PRIVATE = "revocation_reg_def_private" CATEGORY_REV_REG_ISSUER = "revocation_reg_def_issuer" +STATE_REVOCATION_POSTED = "posted" +STATE_REVOCATION_PENDING = "pending" REV_REG_DEF_STATE_ACTIVE = "active" @@ -157,7 +157,6 @@ async def set_active_registry( ) tags = entry.tags - tags["state"] = REV_REG_DEF_STATE_ACTIVE tags["active"] = True await txn.handle.insert( CATEGORY_REV_REG_DEF, diff --git a/aries_cloudagent/indy/credx/issuer.py b/aries_cloudagent/indy/credx/issuer.py index 47c9a6c0e6..27aa853695 100644 --- a/aries_cloudagent/indy/credx/issuer.py +++ b/aries_cloudagent/indy/credx/issuer.py @@ -19,8 +19,6 @@ Schema, ) -from aries_cloudagent.anoncreds.revocation import REV_REG_DEF_STATE_ACTIVE - from ...askar.profile import AskarProfile from ..issuer import ( @@ -594,7 +592,6 @@ async def create_and_store_revocation_registry( rev_reg_def_id = rev_reg_def.id rev_reg_def_json = rev_reg_def.to_json() - rev_reg_def_json["state"] = REV_REG_DEF_STATE_ACTIVE rev_reg_json = rev_reg.to_json() try: @@ -609,7 +606,6 @@ async def create_and_store_revocation_registry( CATEGORY_REV_REG_DEF, rev_reg_def_id, rev_reg_def_json, - {"active": True}, ) await txn.handle.insert( CATEGORY_REV_REG_DEF_PRIVATE, From 6dc59717d145f3a19509a0578e69b5afdbb14b55 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Thu, 1 Jun 2023 12:18:48 -0400 Subject: [PATCH 145/150] fix: store states for rev reg def and rev list Signed-off-by: Daniel Bluhm --- aries_cloudagent/anoncreds/revocation.py | 70 +++++++++++++++++++----- 1 file changed, 56 insertions(+), 14 deletions(-) diff --git a/aries_cloudagent/anoncreds/revocation.py b/aries_cloudagent/anoncreds/revocation.py index f7a8b0bc5b..24a0e1ba0f 100644 --- a/aries_cloudagent/anoncreds/revocation.py +++ b/aries_cloudagent/anoncreds/revocation.py @@ -114,7 +114,13 @@ async def _update_rev_list_record( ) async def _finish_registration( - self, txn: AskarProfileSession, category: str, job_id: str, registered_id: str + self, + txn: AskarProfileSession, + category: str, + job_id: str, + registered_id: str, + *, + state: Optional[str] = None, ): entry = await txn.handle.fetch( category, @@ -126,8 +132,12 @@ async def _finish_registration( f"{category} with job id {job_id} could not be found" ) - tags = entry.tags - tags["state"] = STATE_FINISHED + if state: + tags = entry.tags + tags["state"] = state + else: + tags = entry.tags + await txn.handle.insert( category, registered_id, @@ -234,25 +244,33 @@ async def create_and_register_revocation_registry_definition( self.profile, rev_reg_def, options ) - rev_reg_def_id = result.rev_reg_def_id - rev_reg_def_json = rev_reg_def.to_json() + ident = result.rev_reg_def_id or result.job_id + if not ident: + raise AnonCredsRevocationError( + "Revocation registry definition id or job id not found" + ) + + # TODO Handle `failed` state try: async with self.profile.transaction() as txn: await txn.handle.insert( - CATEGORY_REV_REG_INFO, - rev_reg_def_id, - value_json={"curr_id": 0, "used_ids": []}, + CATEGORY_REV_REG_DEF, + ident, + rev_reg_def.to_json(), + tags={ + "cred_def_id": cred_def_id, + "state": result.revocation_registry_definition_state.state, + }, ) await txn.handle.insert( - CATEGORY_REV_REG_DEF, - rev_reg_def_id, - rev_reg_def_json, - tags={"cred_def_id": cred_def_id}, + CATEGORY_REV_REG_INFO, + ident, + value_json={"curr_id": 0, "used_ids": []}, ) await txn.handle.insert( CATEGORY_REV_REG_DEF_PRIVATE, - rev_reg_def_id, + ident, rev_reg_def_private.to_json_buffer(), ) await txn.commit() @@ -269,7 +287,7 @@ async def finish_revocation_registry_definition( """Mark a rev reg def as finished.""" async with self.profile.transaction() as txn: await self._finish_registration( - txn, CATEGORY_REV_REG_DEF, job_id, rev_reg_def_id + txn, CATEGORY_REV_REG_DEF, job_id, rev_reg_def_id, state=STATE_FINISHED ) await self._finish_registration( txn, @@ -357,12 +375,15 @@ async def create_and_register_revocation_list( self.profile, rev_reg_def, RevList.from_native(rev_list), options ) + # TODO Handle `failed` state + try: async with self.profile.session() as session: await session.handle.insert( CATEGORY_REV_LIST, rev_reg_def_id, result.revocation_list_state.revocation_list.to_json(), + tags={"state": result.revocation_list_state.state}, ) except AskarError as err: raise AnonCredsRevocationError( @@ -371,6 +392,27 @@ async def create_and_register_revocation_list( return result + async def finish_revocation_list(self, rev_reg_def_id: str): + """Mark a revocation list as finished.""" + async with self.profile.transaction() as txn: + entry = await txn.handle.fetch( + CATEGORY_REV_LIST, + rev_reg_def_id, + for_update=True, + ) + if not entry: + raise AnonCredsRevocationError( + f"revocation list with id {rev_reg_def_id} could not be found" + ) + + await txn.handle.replace( + CATEGORY_REV_LIST, + rev_reg_def_id, + value=entry.value, + tags={"state": STATE_FINISHED}, + ) + await txn.commit() + async def retrieve_tails(self, rev_reg_def: RevRegDef) -> str: """Retrieve tails file from server.""" LOGGER.info( From 298790c457558122973ac1cfb3258f175d207157 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Thu, 1 Jun 2023 13:44:44 -0400 Subject: [PATCH 146/150] feat: mark pending on rev list, set active registry Signed-off-by: Daniel Bluhm --- aries_cloudagent/anoncreds/revocation.py | 182 ++++++++++++++++------- 1 file changed, 127 insertions(+), 55 deletions(-) diff --git a/aries_cloudagent/anoncreds/revocation.py b/aries_cloudagent/anoncreds/revocation.py index 24a0e1ba0f..1994256753 100644 --- a/aries_cloudagent/anoncreds/revocation.py +++ b/aries_cloudagent/anoncreds/revocation.py @@ -3,6 +3,7 @@ import asyncio import hashlib import http +import json import logging import os from pathlib import Path @@ -90,29 +91,6 @@ def profile(self) -> AskarProfile: # Revocation artifact management - async def _update_rev_list_record( - self, txn: AskarProfileSession, state, rev_reg_def_id: str - ): - entry = await txn.handle.fetch( - CATEGORY_REV_LIST, - rev_reg_def_id, - for_update=True, - ) - if not entry: - raise AnonCredsRevocationError( - f"{CATEGORY_REV_LIST} with rev_reg_def_id " - f"{rev_reg_def_id} could not be found" - ) - - tags = entry.tags - tags["state"] = state - await txn.handle.insert( - CATEGORY_REV_LIST, - rev_reg_def_id, - value=entry.value, - tags=tags, - ) - async def _finish_registration( self, txn: AskarProfileSession, @@ -146,35 +124,6 @@ async def _finish_registration( ) await txn.handle.remove(category, job_id) - async def mark_pending_revocation(self, txn: AskarProfileSession, rev_reg_id): - await self._update_rev_list_record(txn, STATE_REVOCATION_PENDING, rev_reg_id) - - async def clear_pending_revocation(self, txn: AskarProfileSession, rev_reg_id): - await self._update_rev_list_record(txn, STATE_REVOCATION_POSTED, rev_reg_id) - - async def set_active_registry( - self, txn: AskarProfileSession, state, revoc_reg_id: str - ): - entry = await txn.handle.fetch( - CATEGORY_REV_REG_DEF, - revoc_reg_id, - for_update=True, - ) - if not entry: - raise AnonCredsRevocationError( - f"{CATEGORY_REV_REG_DEF} with revoc_reg_id " - f"{revoc_reg_id} could not be found" - ) - - tags = entry.tags - tags["active"] = True - await txn.handle.insert( - CATEGORY_REV_REG_DEF, - revoc_reg_id, - value=entry.value, - tags=tags, - ) - async def create_and_register_revocation_registry_definition( self, issuer_id: str, @@ -341,6 +290,61 @@ async def get_created_revocation_registry_definition( return None + async def set_active_registry(self, cred_def_id: str, rev_reg_def_id: str): + """Mark a registry as active.""" + async with self.profile.transaction() as txn: + entry = await txn.handle.fetch( + CATEGORY_REV_REG_DEF, + rev_reg_def_id, + for_update=True, + ) + if not entry: + raise AnonCredsRevocationError( + f"{CATEGORY_REV_REG_DEF} with id " + f"{rev_reg_def_id} could not be found" + ) + + if entry.tags["active"] == json.dumps(True): + # NOTE If there are other registries set as active, we're not + # clearing them if the one we want to be active is already + # active. This probably isn't an issue. + return + + old_active_entries = await txn.handle.fetch_all( + CATEGORY_REV_REG_DEF, + { + "active": json.dumps(True), + "cred_def_id": cred_def_id, + }, + for_update=True, + ) + + if len(old_active_entries) > 1: + LOGGER.error( + "More than one registry was set as active for " + f"cred def {cred_def_id}; clearing active tag from all records" + ) + + for old_entry in old_active_entries: + tags = old_entry.tags + tags["active"] = json.dumps(False) + await txn.handle.replace( + CATEGORY_REV_REG_DEF, + old_entry.name, + old_entry.value, + tags, + ) + + tags = entry.tags + tags["active"] = json.dumps(True) + await txn.handle.insert( + CATEGORY_REV_REG_DEF, + rev_reg_def_id, + value=entry.value, + tags=tags, + ) + await txn.commit() + async def create_and_register_revocation_list( self, rev_reg_def_id: str, options: Optional[dict] = None ): @@ -377,12 +381,16 @@ async def create_and_register_revocation_list( # TODO Handle `failed` state + posted = result.revocation_list_state.revocation_list try: async with self.profile.session() as session: await session.handle.insert( CATEGORY_REV_LIST, rev_reg_def_id, - result.revocation_list_state.revocation_list.to_json(), + value_json={ + "posted": posted.serialize(), + "pending": None, + }, tags={"state": result.revocation_list_state.state}, ) except AskarError as err: @@ -413,6 +421,69 @@ async def finish_revocation_list(self, rev_reg_def_id: str): ) await txn.commit() + async def mark_pending_revocations(self, rev_reg_def_id: str, *crids: int): + async with self.profile.transaction() as txn: + entry = await txn.handle.fetch( + CATEGORY_REV_REG_DEF, + rev_reg_def_id, + ) + if not entry: + raise AnonCredsRevocationError( + "Revocation registry definition not found for id {rev_reg_def_id}" + ) + rev_reg_def = RevocationRegistryDefinition.load(entry.value) + + entry = await txn.handle.fetch( + CATEGORY_REV_LIST, + rev_reg_def_id, + for_update=True, + ) + + if not entry: + raise AnonCredsRevocationError( + "Revocation list with id {rev_reg_def_id} not found" + ) + + posted = RevocationStatusList.load(entry.value_json["posted"]) + if entry.value_json["pending"]: + pending = RevocationStatusList.load(entry.value_json["pending"]) + else: + pending = posted + + pending = pending.update( + timestamp=None, issued=None, revoked=crids, rev_reg_def=rev_reg_def + ) + + await txn.handle.replace( + CATEGORY_REV_LIST, + rev_reg_def_id, + value_json={ + "posted": posted.to_dict(), + "pending": pending.to_dict(), + }, + ) + await txn.commit() + + async def clear_pending_revocations(self, rev_reg_def_id: str): + async with self.profile.transaction() as txn: + entry = await txn.handle.fetch( + CATEGORY_REV_LIST, + rev_reg_def_id, + for_update=True, + ) + + if not entry: + raise AnonCredsRevocationError( + "Revocation list with id {rev_reg_def_id} not found" + ) + + await txn.handle.replace( + CATEGORY_REV_LIST, + rev_reg_def_id, + value_json={"posted": entry.value_json["posted"], "pending": None}, + ) + await txn.commit() + async def retrieve_tails(self, rev_reg_def: RevRegDef) -> str: """Retrieve tails file from server.""" LOGGER.info( @@ -531,14 +602,15 @@ async def get_or_create_active_registry(self, cred_def_id: str) -> RevRegDefResu CATEGORY_REV_REG_DEF, { "cred_def_id": cred_def_id, + "active": json.dumps(True), }, + limit=1, ) if not rev_reg_defs: + # TODO Create a registry if none available raise AnonCredsRevocationError("No active registry") - # TODO Sort and return most recent - # TODO Create a registry if none available entry = rev_reg_defs[0] rev_reg_def = RevRegDef.deserialize(entry.value_json) From e3ff93168a7fedb2b855f3340a94d554d3c6cb02 Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Wed, 7 Jun 2023 17:11:09 -0400 Subject: [PATCH 147/150] feat: implement update_revocation_list Signed-off-by: Daniel Bluhm --- anoncreds_test.py | 31 +- aries_cloudagent/anoncreds/base.py | 3 +- .../anoncreds/default/did_indy/registry.py | 3 +- .../anoncreds/default/did_web/registry.py | 3 +- .../anoncreds/default/legacy_indy/registry.py | 10 +- aries_cloudagent/anoncreds/registry.py | 7 +- aries_cloudagent/anoncreds/revocation.py | 368 ++++++++++++------ aries_cloudagent/anoncreds/routes.py | 133 ++++++- aries_cloudagent/revocation/manager.py | 180 ++++----- aries_cloudagent/revocation/routes.py | 10 +- docker-compose.yml | 2 + 11 files changed, 502 insertions(+), 248 deletions(-) diff --git a/anoncreds_test.py b/anoncreds_test.py index 59b7eecde8..3807e935c9 100644 --- a/anoncreds_test.py +++ b/anoncreds_test.py @@ -70,6 +70,9 @@ async def main(): tails = await alice.put( f"/anoncreds/registry/{rev_reg_def_id}/tails-file", ) + active = await alice.put( + f"/anoncreds/registry/{rev_reg_def_id}/active", + ) rev_status_list = await alice.post( "/anoncreds/revocation-list", json={ @@ -79,7 +82,7 @@ async def main(): }, ) alice_conn, bob_conn = await didexchange(alice, bob) - await indy_issue_credential_v2( + alice_cred_ex, bob_cred_ex = await indy_issue_credential_v2( alice, bob, alice_conn.connection_id, @@ -102,6 +105,32 @@ async def main(): ) print(alice_pres.verified) + result = await alice.post( + "/anoncreds/revoke", + json={ + "cred_ex_id": alice_cred_ex.cred_ex_id, + "connection_id": alice_conn.connection_id, + "notify": True, + }, + ) + result = await alice.post( + "/anoncreds/publish-revocations", + ) + bob_pres, alice_pres = await indy_present_proof_v2( + bob, + alice, + bob_conn.connection_id, + alice_conn.connection_id, + name="proof-1", + version="0.1", + comment="testing", + requested_attributes=[ + {"name": "name", "restrictions": [{"cred_def_id": cred_def_id}]}, + {"name": "age", "restrictions": [{"cred_def_id": cred_def_id}]}, + ], + ) + print(alice_pres.verified) + if __name__ == "__main__": import asyncio diff --git a/aries_cloudagent/anoncreds/base.py b/aries_cloudagent/anoncreds/base.py index c2d52cd3bd..bb75c11511 100644 --- a/aries_cloudagent/anoncreds/base.py +++ b/aries_cloudagent/anoncreds/base.py @@ -1,6 +1,6 @@ """Base Registry.""" from abc import ABC, abstractmethod -from typing import Generic, Optional, Pattern, TypeVar +from typing import Generic, Optional, Pattern, Sequence, TypeVar from ..config.injection_context import InjectionContext from ..core.error import BaseError @@ -173,6 +173,7 @@ async def update_revocation_list( rev_reg_def: RevRegDef, prev_list: RevList, curr_list: RevList, + revoked: Sequence[int], options: Optional[dict] = None, ) -> RevListResult: """Update a revocation list on the registry.""" diff --git a/aries_cloudagent/anoncreds/default/did_indy/registry.py b/aries_cloudagent/anoncreds/default/did_indy/registry.py index f022b70d41..1032560644 100644 --- a/aries_cloudagent/anoncreds/default/did_indy/registry.py +++ b/aries_cloudagent/anoncreds/default/did_indy/registry.py @@ -1,7 +1,7 @@ """DID Indy Registry""" import logging import re -from typing import Optional, Pattern +from typing import Optional, Pattern, Sequence from ....config.injection_context import InjectionContext from ....core.profile import Profile @@ -105,6 +105,7 @@ async def update_revocation_list( rev_reg_def: RevRegDef, prev_list: RevList, curr_list: RevList, + revoked: Sequence[int], options: Optional[dict] = None, ) -> RevListResult: """Update a revocation list on the registry.""" diff --git a/aries_cloudagent/anoncreds/default/did_web/registry.py b/aries_cloudagent/anoncreds/default/did_web/registry.py index 197a0d2a1b..fb2999c256 100644 --- a/aries_cloudagent/anoncreds/default/did_web/registry.py +++ b/aries_cloudagent/anoncreds/default/did_web/registry.py @@ -1,6 +1,6 @@ """DID Web Registry""" import re -from typing import Optional, Pattern +from typing import Optional, Pattern, Sequence from ....config.injection_context import InjectionContext from ....core.profile import Profile @@ -100,6 +100,7 @@ async def update_revocation_list( rev_reg_def: RevRegDef, prev_list: RevList, curr_list: RevList, + revoked: Sequence[int], options: Optional[dict] = None, ) -> RevListResult: """Update a revocation list on the registry.""" diff --git a/aries_cloudagent/anoncreds/default/legacy_indy/registry.py b/aries_cloudagent/anoncreds/default/legacy_indy/registry.py index b8646c864f..ad3a8c3eb7 100644 --- a/aries_cloudagent/anoncreds/default/legacy_indy/registry.py +++ b/aries_cloudagent/anoncreds/default/legacy_indy/registry.py @@ -3,7 +3,7 @@ import json import logging import re -from typing import Optional, Pattern, Tuple +from typing import Optional, Pattern, Sequence, Tuple from ....config.injection_context import InjectionContext from ....core.profile import Profile @@ -542,16 +542,14 @@ async def update_revocation_list( rev_reg_def: RevRegDef, prev_list: RevList, curr_list: RevList, + revoked: Sequence[int], options: Optional[dict] = None, ) -> RevListResult: """Update a revocation list.""" newly_revoked_indices = [ # Remember: Indices in Indy are 1-based - index + 1 - for index, (prev, curr) in enumerate( - zip(prev_list.revocation_list, curr_list.revocation_list) - ) - if prev != curr + index # + 1 TODO This needs to be offset! Commented for testing + for index in revoked ] rev_reg_entry = { "ver": "1.0", diff --git a/aries_cloudagent/anoncreds/registry.py b/aries_cloudagent/anoncreds/registry.py index 1412471ee3..654f257680 100644 --- a/aries_cloudagent/anoncreds/registry.py +++ b/aries_cloudagent/anoncreds/registry.py @@ -1,6 +1,6 @@ """AnonCreds Registry""" import logging -from typing import List, Optional +from typing import List, Optional, Sequence from ..core.profile import Profile @@ -168,10 +168,11 @@ async def update_revocation_list( rev_reg_def: RevRegDef, prev_list: RevList, curr_list: RevList, + revoked: Sequence[int], options: Optional[dict] = None, ) -> RevListResult: """Update a revocation list on the registry.""" - registrar = await self._registrar_for_identifier(curr_list.issuer_id) + registrar = await self._registrar_for_identifier(prev_list.issuer_id) return await registrar.update_revocation_list( - profile, rev_reg_def, prev_list, curr_list, options + profile, rev_reg_def, prev_list, curr_list, revoked, options ) diff --git a/aries_cloudagent/anoncreds/revocation.py b/aries_cloudagent/anoncreds/revocation.py index 1994256753..94d77b1360 100644 --- a/aries_cloudagent/anoncreds/revocation.py +++ b/aries_cloudagent/anoncreds/revocation.py @@ -25,7 +25,7 @@ from ..askar.profile import AskarProfile, AskarProfileSession from ..core.error import BaseError -from ..core.profile import Profile +from ..core.profile import Profile, ProfileSession from ..tails.base import BaseTailsServer from .issuer import ( AnonCredsIssuer, @@ -45,7 +45,6 @@ LOGGER = logging.getLogger(__name__) CATEGORY_REV_LIST = "revocation_list" -CATEGORY_REV_REG_INFO = "revocation_reg_info" CATEGORY_REV_REG_DEF = "revocation_reg_def" CATEGORY_REV_REG_DEF_PRIVATE = "revocation_reg_def_private" CATEGORY_REV_REG_ISSUER = "revocation_reg_def_issuer" @@ -63,8 +62,9 @@ class AnonCredsRevocationRegistryFullError(AnonCredsRevocationError): class RevokeResult(NamedTuple): - prev: Optional[RevocationStatusList] = None - curr: Optional[RevocationStatusList] = None + prev: RevList + curr: Optional[RevList] = None + revoked: Optional[Sequence[int]] = None failed: Optional[Sequence[str]] = None @@ -210,13 +210,9 @@ async def create_and_register_revocation_registry_definition( tags={ "cred_def_id": cred_def_id, "state": result.revocation_registry_definition_state.state, + "active": json.dumps(False), }, ) - await txn.handle.insert( - CATEGORY_REV_REG_INFO, - ident, - value_json={"curr_id": 0, "used_ids": []}, - ) await txn.handle.insert( CATEGORY_REV_REG_DEF_PRIVATE, ident, @@ -238,12 +234,6 @@ async def finish_revocation_registry_definition( await self._finish_registration( txn, CATEGORY_REV_REG_DEF, job_id, rev_reg_def_id, state=STATE_FINISHED ) - await self._finish_registration( - txn, - CATEGORY_REV_REG_INFO, - job_id, - rev_reg_def_id, - ) await self._finish_registration( txn, CATEGORY_REV_REG_DEF_PRIVATE, @@ -290,7 +280,7 @@ async def get_created_revocation_registry_definition( return None - async def set_active_registry(self, cred_def_id: str, rev_reg_def_id: str): + async def set_active_registry(self, rev_reg_def_id: str): """Mark a registry as active.""" async with self.profile.transaction() as txn: entry = await txn.handle.fetch( @@ -310,6 +300,8 @@ async def set_active_registry(self, cred_def_id: str, rev_reg_def_id: str): # active. This probably isn't an issue. return + cred_def_id = entry.tags["cred_def_id"] + old_active_entries = await txn.handle.fetch_all( CATEGORY_REV_REG_DEF, { @@ -337,7 +329,7 @@ async def set_active_registry(self, cred_def_id: str, rev_reg_def_id: str): tags = entry.tags tags["active"] = json.dumps(True) - await txn.handle.insert( + await txn.handle.replace( CATEGORY_REV_REG_DEF, rev_reg_def_id, value=entry.value, @@ -356,7 +348,7 @@ async def create_and_register_revocation_list( ) except AskarError as err: raise AnonCredsRevocationError( - "Error retrieving credential definition" + "Error retrieving revocation registry definition" ) from err if not rev_reg_def_entry: @@ -381,17 +373,22 @@ async def create_and_register_revocation_list( # TODO Handle `failed` state - posted = result.revocation_list_state.revocation_list + rev_list = result.revocation_list_state.revocation_list try: async with self.profile.session() as session: await session.handle.insert( CATEGORY_REV_LIST, rev_reg_def_id, value_json={ - "posted": posted.serialize(), + "rev_list": rev_list.serialize(), "pending": None, + # TODO THIS IS A HACK; this fixes ACA-Py expecting 1-based indexes + "next_index": 1, + }, + tags={ + "state": result.revocation_list_state.state, + "pending": json.dumps(False), }, - tags={"state": result.revocation_list_state.state}, ) except AskarError as err: raise AnonCredsRevocationError( @@ -413,76 +410,123 @@ async def finish_revocation_list(self, rev_reg_def_id: str): f"revocation list with id {rev_reg_def_id} could not be found" ) + tags = entry.tags + tags["state"] = STATE_FINISHED await txn.handle.replace( CATEGORY_REV_LIST, rev_reg_def_id, value=entry.value, - tags={"state": STATE_FINISHED}, + tags=tags, ) await txn.commit() - async def mark_pending_revocations(self, rev_reg_def_id: str, *crids: int): - async with self.profile.transaction() as txn: - entry = await txn.handle.fetch( - CATEGORY_REV_REG_DEF, - rev_reg_def_id, - ) - if not entry: - raise AnonCredsRevocationError( - "Revocation registry definition not found for id {rev_reg_def_id}" + async def update_revocation_list( + self, + rev_reg_def_id: str, + prev: RevList, + curr: RevList, + revoked: Sequence[int], + options: Optional[dict] = None, + ): + """Publish and update to a revocation list.""" + try: + async with self.profile.session() as session: + rev_reg_def_entry = await session.handle.fetch( + CATEGORY_REV_REG_DEF, rev_reg_def_id ) - rev_reg_def = RevocationRegistryDefinition.load(entry.value) + except AskarError as err: + raise AnonCredsRevocationError( + "Error retrieving revocation registry definition" + ) from err - entry = await txn.handle.fetch( - CATEGORY_REV_LIST, - rev_reg_def_id, - for_update=True, + if not rev_reg_def_entry: + raise AnonCredsRevocationError( + f"Revocation registry definition not found for id {rev_reg_def_id}" ) - if not entry: - raise AnonCredsRevocationError( - "Revocation list with id {rev_reg_def_id} not found" + try: + async with self.profile.session() as session: + rev_list_entry = await session.handle.fetch( + CATEGORY_REV_LIST, rev_reg_def_id ) + except AskarError as err: + raise AnonCredsRevocationError("Error retrieving revocation list") from err - posted = RevocationStatusList.load(entry.value_json["posted"]) - if entry.value_json["pending"]: - pending = RevocationStatusList.load(entry.value_json["pending"]) - else: - pending = posted - - pending = pending.update( - timestamp=None, issued=None, revoked=crids, rev_reg_def=rev_reg_def + if not rev_list_entry: + raise AnonCredsRevocationError( + f"Revocation list not found for id {rev_reg_def_id}" ) - await txn.handle.replace( - CATEGORY_REV_LIST, - rev_reg_def_id, - value_json={ - "posted": posted.to_dict(), - "pending": pending.to_dict(), - }, + rev_reg_def = RevRegDef.deserialize(rev_reg_def_entry.value_json) + rev_list = RevList.deserialize(rev_list_entry.value_json["rev_list"]) + if rev_list.revocation_list != curr.revocation_list: + raise AnonCredsRevocationError( + "Passed revocation list does not match stored" ) - await txn.commit() - async def clear_pending_revocations(self, rev_reg_def_id: str): - async with self.profile.transaction() as txn: - entry = await txn.handle.fetch( - CATEGORY_REV_LIST, - rev_reg_def_id, - for_update=True, - ) + anoncreds_registry = self.profile.inject(AnonCredsRegistry) + result = await anoncreds_registry.update_revocation_list( + self.profile, rev_reg_def, prev, curr, revoked, options + ) - if not entry: - raise AnonCredsRevocationError( - "Revocation list with id {rev_reg_def_id} not found" + # TODO Handle `failed` state + + try: + async with self.profile.session() as session: + rev_list_entry_upd = await session.handle.fetch( + CATEGORY_REV_LIST, rev_reg_def_id, for_update=True ) + if not rev_list_entry_upd: + raise AnonCredsRevocationError( + "Revocation list not found for id {rev_reg_def_id}" + ) + tags = rev_list_entry_upd.tags + tags["state"] = result.revocation_list_state.state + await session.handle.replace( + CATEGORY_REV_LIST, + rev_reg_def_id, + value=rev_list_entry_upd.value, + tags=tags, + ) + except AskarError as err: + raise AnonCredsRevocationError( + "Error saving new revocation registry" + ) from err - await txn.handle.replace( - CATEGORY_REV_LIST, - rev_reg_def_id, - value_json={"posted": entry.value_json["posted"], "pending": None}, - ) - await txn.commit() + return result + + async def get_created_revocation_list( + self, rev_reg_def_id: str + ) -> Optional[RevList]: + """Return rev list from record in wallet.""" + try: + async with self.profile.session() as session: + rev_list_entry = await session.handle.fetch( + CATEGORY_REV_LIST, rev_reg_def_id + ) + except AskarError as err: + raise AnonCredsRevocationError("Error retrieving revocation list") from err + + if rev_list_entry: + return RevList.deserialize(rev_list_entry.value_json["rev_list"]) + + return None + + async def get_revocation_lists_with_pending_revocations(self) -> Sequence[str]: + """Return a list of rev reg def ids with pending revocations.""" + try: + async with self.profile.session() as session: + rev_list_entries = await session.handle.fetch_all( + CATEGORY_REV_LIST, + {"pending": json.dumps(True)}, + ) + except AskarError as err: + raise AnonCredsRevocationError("Error retrieving revocation list") from err + + if rev_list_entries: + return [entry.name for entry in rev_list_entries] + + return [] async def retrieve_tails(self, rev_reg_def: RevRegDef) -> str: """Retrieve tails file from server.""" @@ -673,9 +717,6 @@ async def _create_credential( try: async with self.profile.transaction() as txn: rev_list = await txn.handle.fetch(CATEGORY_REV_LIST, rev_reg_def_id) - rev_reg_info = await txn.handle.fetch( - CATEGORY_REV_REG_INFO, rev_reg_def_id, for_update=True - ) rev_reg_def = await txn.handle.fetch( CATEGORY_REV_REG_DEF, rev_reg_def_id ) @@ -684,10 +725,6 @@ async def _create_credential( ) if not rev_list: raise AnonCredsRevocationError("Revocation registry not found") - if not rev_reg_info: - raise AnonCredsRevocationError( - "Revocation registry metadata not found" - ) if not rev_reg_def: raise AnonCredsRevocationError( "Revocation registry definition not found" @@ -701,13 +738,14 @@ async def _create_credential( # be updated because we always use ISSUANCE_BY_DEFAULT. # If something goes wrong later, the index will be skipped. # FIXME - double check issuance type in case of upgraded wallet? - rev_info = rev_reg_info.value_json - rev_reg_index = rev_info["curr_id"] + 1 + rev_info = rev_list.value_json + rev_info_tags = rev_list.tags + rev_reg_index = rev_info["next_index"] try: rev_reg_def = RevocationRegistryDefinition.load( rev_reg_def.raw_value ) - rev_list = RevocationStatusList.load(rev_list.raw_value) + rev_list = RevocationStatusList.load(rev_info["rev_list"]) except AnoncredsError as err: raise AnonCredsRevocationError( "Error loading revocation registry definition" @@ -716,9 +754,12 @@ async def _create_credential( raise AnonCredsRevocationRegistryFullError( "Revocation registry is full" ) - rev_info["curr_id"] = rev_reg_index + rev_info["next_index"] = rev_reg_index + 1 await txn.handle.replace( - CATEGORY_REV_REG_INFO, rev_reg_def_id, value_json=rev_info + CATEGORY_REV_LIST, + rev_reg_def_id, + value_json=rev_info, + tags=rev_info_tags, ) await txn.commit() except AskarError as err: @@ -841,28 +882,27 @@ async def create_credential( f"Cred def '{cred_def_id}' has no active revocation registry" ) - async def revoke_credentials( + async def revoke_pending_credentials( self, revoc_reg_id: str, - tails_file_path: str, - cred_revoc_ids: Sequence[str], + *, + additional_crids: Optional[Sequence[int]] = None, + limit_crids: Optional[Sequence[int]] = None, ) -> RevokeResult: """ Revoke a set of credentials in a revocation registry. Args: revoc_reg_id: ID of the revocation registry - tails_file_path: path to the local tails file - cred_revoc_ids: sequences of credential indexes in the revocation registry + additional_crids: sequences of additional credential indexes to revoke + limit_crids: a sequence of credential indexes to limit revocation to + If None, all pending revocations will be published. + If given, the intersection of pending and limit crids will be published. Returns: Tuple with the update revocation list, list of cred rev ids not revoked """ - - # TODO This method should return the old list, the new list, - # and the list of changed indices - prev_list = None updated_list = None failed_crids = set() max_attempt = 5 @@ -882,27 +922,23 @@ async def revoke_credentials( rev_list_entry = await session.handle.fetch( CATEGORY_REV_LIST, revoc_reg_id ) - rev_reg_info = await session.handle.fetch( - CATEGORY_REV_REG_INFO, revoc_reg_id - ) if not rev_reg_def_entry: raise AnonCredsRevocationError( "Revocation registry definition not found" ) if not rev_list_entry: raise AnonCredsRevocationError("Revocation registry not found") - if not rev_reg_info: - raise AnonCredsRevocationError( - "Revocation registry metadata not found" - ) except AskarError as err: raise AnonCredsRevocationError( "Error retrieving revocation registry" ) from err try: - rev_reg_def = RevocationRegistryDefinition.load( - rev_reg_def_entry.raw_value + # TODO This is a little rough; stored tails location will have public uri + # but library needs local tails location + rev_reg_def = RevRegDef.deserialize(rev_reg_def_entry.value_json) + rev_reg_def.value.tails_location = self.get_local_tails_path( + rev_reg_def ) except AnoncredsError as err: raise AnonCredsRevocationError( @@ -911,12 +947,12 @@ async def revoke_credentials( rev_crids = set() failed_crids = set() - max_cred_num = rev_reg_def.max_cred_num - rev_info = rev_reg_info.value_json - used_ids = set(rev_info.get("used_ids") or []) + max_cred_num = rev_reg_def.value.max_cred_num + rev_info = rev_list_entry.value_json + cred_revoc_ids = rev_info["pending"] + (additional_crids or []) + rev_list = RevList.deserialize(rev_info["rev_list"]) for rev_id in cred_revoc_ids: - rev_id = int(rev_id) if rev_id < 1 or rev_id > max_cred_num: LOGGER.error( "Skipping requested credential revocation" @@ -925,7 +961,7 @@ async def revoke_credentials( rev_id, ) failed_crids.add(rev_id) - elif rev_id > rev_info["curr_id"]: + elif rev_id >= rev_info["next_index"]: LOGGER.warn( "Skipping requested credential revocation" "on rev reg id %s, cred rev id=%s not yet issued", @@ -933,7 +969,7 @@ async def revoke_credentials( rev_id, ) failed_crids.add(rev_id) - elif rev_id in used_ids: + elif rev_list.revocation_list[rev_id] == 1: LOGGER.warn( "Skipping requested credential revocation" "on rev reg id %s, cred rev id=%s already revoked", @@ -947,21 +983,20 @@ async def revoke_credentials( if not rev_crids: break - try: - prev_list = RevocationStatusList.load(rev_list_entry.raw_value) - except AnoncredsError as err: - raise AnonCredsRevocationError( - "Error loading revocation registry" - ) from err + if limit_crids is None: + skipped_crids = set() + else: + skipped_crids = rev_crids - set(limit_crids) + rev_crids = rev_crids - skipped_crids try: updated_list = await asyncio.get_event_loop().run_in_executor( None, - lambda: prev_list.update( + lambda: rev_list.to_native().update( int(time.time()), None, # issued list(rev_crids), # revoked - rev_reg_def, + rev_reg_def.to_native(), ), ) except AnoncredsError as err: @@ -971,32 +1006,31 @@ async def revoke_credentials( try: async with self.profile.transaction() as txn: - rev_list_upd = await txn.handle.fetch( - CATEGORY_REV_LIST, revoc_reg_id, for_update=True - ) rev_info_upd = await txn.handle.fetch( - CATEGORY_REV_REG_INFO, revoc_reg_id, for_update=True + CATEGORY_REV_LIST, revoc_reg_id, for_update=True ) - if not rev_list_upd or not rev_reg_info: + if not rev_info_upd: LOGGER.warn( "Revocation registry missing, skipping update: {}", revoc_reg_id, ) updated_list = None break + tags = rev_info_upd.tags rev_info_upd = rev_info_upd.value_json if rev_info_upd != rev_info: # handle concurrent update to the registry by retrying continue + rev_info_upd["rev_list"] = updated_list.to_dict() + rev_info_upd["pending"] = ( + list(skipped_crids) if skipped_crids else None + ) + tags["pending"] = json.dumps(True if skipped_crids else False) await txn.handle.replace( CATEGORY_REV_LIST, revoc_reg_id, - updated_list.to_json_buffer(), - ) - used_ids.update(rev_crids) - rev_info_upd["used_ids"] = sorted(used_ids) - await txn.handle.replace( - CATEGORY_REV_REG_INFO, revoc_reg_id, value_json=rev_info_upd + value_json=rev_info_upd, + tags=tags, ) await txn.commit() except AskarError as err: @@ -1006,7 +1040,85 @@ async def revoke_credentials( break return RevokeResult( - prev=prev_list, - curr=updated_list, + prev=rev_list, + curr=RevList.from_native(updated_list) if updated_list else None, + revoked=list(rev_crids), failed=[str(rev_id) for rev_id in sorted(failed_crids)], ) + + async def mark_pending_revocations(self, rev_reg_def_id: str, *crids: int): + """Stores the cred rev ids to publish later.""" + async with self.profile.transaction() as txn: + entry = await txn.handle.fetch( + CATEGORY_REV_LIST, + rev_reg_def_id, + for_update=True, + ) + + if not entry: + raise AnonCredsRevocationError( + "Revocation list with id {rev_reg_def_id} not found" + ) + + pending: Optional[List[int]] = entry.value_json["pending"] + if pending: + pending.extend(crids) + else: + pending = list(crids) + + value = entry.value_json + value["pending"] = pending + tags = entry.tags + tags["pending"] = json.dumps(True) + await txn.handle.replace( + CATEGORY_REV_LIST, + rev_reg_def_id, + value_json=value, + tags=tags, + ) + await txn.commit() + + async def get_pending_revocations(self, rev_reg_def_id: str) -> List[int]: + """Retrieve the list of credential revocation ids pending revocation.""" + async with self.profile.session() as session: + entry = await session.handle.fetch(CATEGORY_REV_LIST, rev_reg_def_id) + if not entry: + return [] + + return entry.value_json["pending"] or [] + + async def clear_pending_revocations( + self, + txn: ProfileSession, + rev_reg_def_id: str, + crid_mask: Optional[Sequence[int]] = None, + ): + """Clear pending revocations.""" + if not isinstance(txn, AskarProfileSession): + raise ValueError("Askar wallet required") + + entry = await txn.handle.fetch( + CATEGORY_REV_LIST, + rev_reg_def_id, + for_update=True, + ) + + if not entry: + raise AnonCredsRevocationError( + "Revocation list with id {rev_reg_def_id} not found" + ) + + value = entry.value_json + if crid_mask is None: + value["pending"] = None + else: + value["pending"] = set(value["pending"]) - set(crid_mask) + + tags = entry.tags + tags["pending"] = json.dumps(False) + await txn.handle.replace( + CATEGORY_REV_LIST, + rev_reg_def_id, + value_json=value, + tags=tags, + ) diff --git a/aries_cloudagent/anoncreds/routes.py b/aries_cloudagent/anoncreds/routes.py index 5c5dca0ca5..ea1e54cf8d 100644 --- a/aries_cloudagent/anoncreds/routes.py +++ b/aries_cloudagent/anoncreds/routes.py @@ -16,9 +16,17 @@ from ..askar.profile import AskarProfile from ..messaging.models.openapi import OpenAPISchema from ..messaging.valid import UUIDFour -from ..revocation.error import RevocationNotSupportedError -from ..revocation.routes import RevRegIdMatchInfoSchema, RevocationModuleResponseSchema -from ..storage.error import StorageNotFoundError +from ..revocation.error import RevocationError, RevocationNotSupportedError +from ..revocation.manager import RevocationManager, RevocationManagerError +from ..revocation.routes import ( + PublishRevocationsSchema, + RevRegIdMatchInfoSchema, + RevocationModuleResponseSchema, + RevokeRequestSchema, + TxnOrPublishRevocationsResultSchema, +) +from ..storage.error import StorageError, StorageNotFoundError +from .base import AnonCredsRegistrationError from .issuer import AnonCredsIssuer, AnonCredsIssuerError from .models.anoncreds_cred_def import CredDefResultSchema, GetCredDefResultSchema from .models.anoncreds_revocation import RevListResultSchema, RevRegDefResultSchema @@ -410,7 +418,7 @@ async def rev_list_post(request: web.BaseRequest): @docs( - tags=["revocation"], + tags=["anoncreds"], summary="Upload local tails file to server", ) @match_info_schema(RevRegIdMatchInfoSchema()) @@ -442,6 +450,120 @@ async def upload_tails_file(request: web.BaseRequest): return web.json_response({}) +@docs( + tags=["anoncreds"], + summary="Upload local tails file to server", +) +@match_info_schema(RevRegIdMatchInfoSchema()) +@response_schema(RevocationModuleResponseSchema(), description="") +async def set_active_registry(request: web.BaseRequest): + """ + Request handler to upload local tails file for revocation registry. + + Args: + request: aiohttp request object + + """ + context: AdminRequestContext = request["context"] + rev_reg_id = request.match_info["rev_reg_id"] + try: + revocation = AnonCredsRevocation(context.profile) + await revocation.set_active_registry(rev_reg_id) + except AnonCredsRevocationError as e: + raise web.HTTPInternalServerError(reason=str(e)) from e + + return web.json_response({}) + + +@docs( + tags=["anoncreds"], + summary="Revoke an issued credential", +) +@request_schema(RevokeRequestSchema()) +@response_schema(RevocationModuleResponseSchema(), description="") +async def revoke(request: web.BaseRequest): + """ + Request handler for storing a credential revocation. + + Args: + request: aiohttp request object + + Returns: + The credential revocation details. + + """ + context: AdminRequestContext = request["context"] + body = await request.json() + cred_ex_id = body.get("cred_ex_id") + body["notify"] = body.get("notify", context.settings.get("revocation.notify")) + notify = body.get("notify") + connection_id = body.get("connection_id") + body["notify_version"] = body.get("notify_version", "v1_0") + notify_version = body["notify_version"] + + if notify and not connection_id: + raise web.HTTPBadRequest(reason="connection_id must be set when notify is true") + if notify and not notify_version: + raise web.HTTPBadRequest( + reason="Request must specify notify_version if notify is true" + ) + + rev_manager = RevocationManager(context.profile) + try: + if cred_ex_id: + # rev_reg_id and cred_rev_id should not be present so we can + # safely splat the body + await rev_manager.revoke_credential_by_cred_ex_id(**body) + else: + # no cred_ex_id so we can safely splat the body + await rev_manager.revoke_credential(**body) + except ( + RevocationManagerError, + AnonCredsRevocationError, + StorageError, + AnonCredsIssuerError, + AnonCredsRegistrationError, + ) as err: + raise web.HTTPBadRequest(reason=err.roll_up) from err + + return web.json_response({}) + + +@docs(tags=["revocation"], summary="Publish pending revocations to ledger") +@request_schema(PublishRevocationsSchema()) +@response_schema(TxnOrPublishRevocationsResultSchema(), 200, description="") +async def publish_revocations(request: web.BaseRequest): + """ + Request handler for publishing pending revocations to the ledger. + + Args: + request: aiohttp request object + + Returns: + Credential revocation ids published as revoked by revocation registry id. + + """ + context: AdminRequestContext = request["context"] + body = await request.json() + rrid2crid = body.get("rrid2crid") + + rev_manager = RevocationManager(context.profile) + + try: + rev_reg_resp = await rev_manager.publish_pending_revocations( + rrid2crid, + ) + except ( + RevocationError, + StorageError, + AnonCredsIssuerError, + AnonCredsRevocationError, + ) as err: + raise web.HTTPBadRequest(reason=err.roll_up) from err + + return web.json_response({"rrid2crid": rev_reg_resp}) + + async def register(app: web.Application): """Register routes.""" @@ -464,6 +586,9 @@ async def register(app: web.Application): web.post("/anoncreds/revocation-registry-definition", rev_reg_def_post), web.post("/anoncreds/revocation-list", rev_list_post), web.put("/anoncreds/registry/{rev_reg_id}/tails-file", upload_tails_file), + web.put("/anoncreds/registry/{rev_reg_id}/active", set_active_registry), + web.post("/anoncreds/revoke", revoke), + web.post("/anoncreds/publish-revocations", publish_revocations), ] ) diff --git a/aries_cloudagent/revocation/manager.py b/aries_cloudagent/revocation/manager.py index 72f0a210ba..cb174a8628 100644 --- a/aries_cloudagent/revocation/manager.py +++ b/aries_cloudagent/revocation/manager.py @@ -1,25 +1,22 @@ """Classes to manage credential revocation.""" import logging -from typing import Mapping, Sequence, Text +from typing import Mapping, Optional, Sequence, Text, Tuple -from ..protocols.revocation_notification.v1_0.models.rev_notification_record import ( - RevNotificationRecord, -) +from ..anoncreds.default.legacy_indy.registry import LegacyIndyRegistry +from ..anoncreds.revocation import AnonCredsRevocation from ..core.error import BaseError from ..core.profile import Profile -from ..anoncreds.issuer import AnonCredsIssuer -from ..storage.error import StorageNotFoundError -from .anoncreds import AnonCredsRevocation -from .models.issuer_cred_rev_record import IssuerCredRevRecord -from .models.issuer_rev_reg_record import IssuerRevRegRecord -from .util import notify_pending_cleared_event, notify_revocation_published_event from ..protocols.issue_credential.v1_0.models.credential_exchange import ( V10CredentialExchange, ) -from ..protocols.issue_credential.v2_0.models.cred_ex_record import ( - V20CredExRecord, +from ..protocols.issue_credential.v2_0.models.cred_ex_record import V20CredExRecord +from ..protocols.revocation_notification.v1_0.models.rev_notification_record import ( + RevNotificationRecord, ) +from ..storage.error import StorageNotFoundError +from .models.issuer_cred_rev_record import IssuerCredRevRecord +from .util import notify_pending_cleared_event, notify_revocation_published_event class RevocationManagerError(BaseError): @@ -106,15 +103,32 @@ async def revoke_credential( along with any revocations pending against it """ - issuer = AnonCredsIssuer(self._profile) - revoc = AnonCredsRevocation(self._profile) - issuer_rr_rec = await revoc.get_issuer_rev_reg_record(rev_reg_id) - if not issuer_rr_rec: + rev_reg_def = await revoc.get_created_revocation_registry_definition(rev_reg_id) + if not rev_reg_def: raise RevocationManagerError( f"No revocation registry record found for id: {rev_reg_id}" ) + if publish: + await revoc.get_or_fetch_local_tails_path(rev_reg_def) + result = await revoc.revoke_pending_credentials( + rev_reg_id, + additional_crids=[int(cred_rev_id)], + ) + + if result.curr and result.revoked: + await self.set_cred_revoked_state(rev_reg_id, result.revoked) + await revoc.update_revocation_list( + rev_reg_id, result.prev, result.curr, result.revoked + ) + await notify_revocation_published_event( + self._profile, rev_reg_id, [cred_rev_id] + ) + + else: + await revoc.mark_pending_revocations(rev_reg_id, int(cred_rev_id)) + if notify: thread_id = thread_id or f"indy::{rev_reg_id}::{cred_rev_id}" rev_notify_rec = RevNotificationRecord( @@ -128,44 +142,17 @@ async def revoke_credential( async with self._profile.session() as session: await rev_notify_rec.save(session, reason="New revocation notification") - if publish: - rev_reg = await revoc.get_revocation_registry(rev_reg_id) - await rev_reg.get_or_fetch_local_tails_path() - # pick up pending revocations on input revocation registry - crids = (issuer_rr_rec.pending_pub or []) + [cred_rev_id] - (prev, curr, _) = await issuer.revoke_credentials( - issuer_rr_rec.revoc_reg_id, issuer_rr_rec.tails_local_path, crids - ) - async with self._profile.transaction() as txn: - issuer_rr_upd = await IssuerRevRegRecord.retrieve_by_id( - txn, issuer_rr_rec.record_id, for_update=True - ) - if prev: - issuer_rr_upd.prev_list = prev - issuer_rr_upd.rev_list = curr - await issuer_rr_upd.clear_pending(txn, crids) - await txn.commit() - await self.set_cred_revoked_state(rev_reg_id, crids) - if prev: - await issuer_rr_upd.send_entry(self._profile) - await notify_revocation_published_event( - self._profile, rev_reg_id, [cred_rev_id] - ) - - else: - async with self._profile.transaction() as txn: - await issuer_rr_rec.mark_pending(txn, cred_rev_id) - await txn.commit() - async def update_rev_reg_revoked_state( self, + rev_reg_def_id: str, apply_ledger_update: bool, - rev_reg_record: IssuerRevRegRecord, - genesis_transactions: dict, - ) -> (dict, dict, dict): + genesis_transactions: str, + ) -> Tuple[dict, dict, dict]: """ Request handler to fix ledger entry of credentials revoked against registry. + This is an indy registry specific operation. + Args: rev_reg_id: revocation registry id apply_ledger_update: whether to apply an update to the ledger @@ -174,15 +161,31 @@ async def update_rev_reg_revoked_state( Number of credentials posted to ledger """ - return await rev_reg_record.fix_ledger_entry( - self._profile, - apply_ledger_update, - genesis_transactions, + revoc = AnonCredsRevocation(self._profile) + rev_list = await revoc.get_created_revocation_list(rev_reg_def_id) + if not rev_list: + raise RevocationManagerError( + f"No revocation list found for revocation registry id {rev_reg_def_id}" + ) + + indy_registry = LegacyIndyRegistry() + + if await indy_registry.supports(rev_reg_def_id): + return await indy_registry.fix_ledger_entry( + self._profile, + rev_list, + apply_ledger_update, + genesis_transactions, + ) + + raise RevocationManagerError( + "Indy registry does not support revocation registry " + f"identified by {rev_reg_def_id}" ) async def publish_pending_revocations( self, - rrid2crid: Mapping[Text, Sequence[Text]] = None, + rrid2crid: Optional[Mapping[Text, Sequence[Text]]] = None, ) -> Mapping[Text, Sequence[Text]]: """ Publish pending revocations to the ledger. @@ -207,48 +210,32 @@ async def publish_pending_revocations( Returns: mapping from each revocation registry id to its cred rev ids published. """ - result = {} - issuer = AnonCredsIssuer(self._profile) - - async with self._profile.session() as session: - issuer_rr_recs = await IssuerRevRegRecord.query_by_pending(session) + published_crids = {} + revoc = AnonCredsRevocation(self._profile) - for issuer_rr_rec in issuer_rr_recs: - rrid = issuer_rr_rec.revoc_reg_id + rev_reg_def_ids = await revoc.get_revocation_lists_with_pending_revocations() + for rrid in rev_reg_def_ids: if rrid2crid: if rrid not in rrid2crid: continue - limit_crids = rrid2crid[rrid] + limit_crids = [int(crid) for crid in rrid2crid[rrid]] else: - limit_crids = () - crids = set(issuer_rr_rec.pending_pub or ()) - if limit_crids: - crids = crids.intersection(limit_crids) - if crids: - (prev, curr, failed_crids) = await issuer.revoke_credentials( - issuer_rr_rec.revoc_reg_id, - issuer_rr_rec.tails_local_path, - crids, + limit_crids = None + + result = await revoc.revoke_pending_credentials( + rrid, limit_crids=limit_crids + ) + if result.curr and result.revoked: + await self.set_cred_revoked_state(rrid, result.revoked) + await revoc.update_revocation_list( + rrid, result.prev, result.curr, result.revoked ) - async with self._profile.transaction() as txn: - issuer_rr_upd = await IssuerRevRegRecord.retrieve_by_id( - txn, issuer_rr_rec.record_id, for_update=True - ) - if prev: - issuer_rr_upd.prev_list = prev - issuer_rr_upd.rev_list = curr - await issuer_rr_upd.clear_pending(txn, crids) - await txn.commit() - await self.set_cred_revoked_state(issuer_rr_rec.revoc_reg_id, crids) - if prev: - await issuer_rr_upd.send_entry(self._profile) - published = sorted(crid for crid in crids if crid not in failed_crids) - result[issuer_rr_rec.revoc_reg_id] = published + published_crids[rrid] = sorted(result.revoked) await notify_revocation_published_event( - self._profile, issuer_rr_rec.revoc_reg_id, crids + self._profile, rrid, [str(crid) for crid in result.revoked] ) - return result + return published_crids async def clear_pending_revocations( self, purge: Mapping[Text, Sequence[Text]] = None @@ -283,13 +270,18 @@ async def clear_pending_revocations( result = {} notify = [] + revoc = AnonCredsRevocation(self._profile) + rrids = await revoc.get_revocation_lists_with_pending_revocations() async with self._profile.transaction() as txn: - issuer_rr_recs = await IssuerRevRegRecord.query_by_pending(txn) - for issuer_rr_rec in issuer_rr_recs: - rrid = issuer_rr_rec.revoc_reg_id - await issuer_rr_rec.clear_pending(txn, (purge or {}).get(rrid)) - if issuer_rr_rec.pending_pub: - result[rrid] = issuer_rr_rec.pending_pub + for rrid in rrids: + await revoc.clear_pending_revocations( + txn, + rrid, + crid_mask=[int(crid) for crid in (purge or {}).get(rrid, ())], + ) + remaining = await revoc.get_pending_revocations(rrid) + if remaining: + result[rrid] = remaining notify.append(rrid) await txn.commit() @@ -299,7 +291,7 @@ async def clear_pending_revocations( return result async def set_cred_revoked_state( - self, rev_reg_id: str, cred_rev_ids: Sequence[str] + self, rev_reg_id: str, cred_rev_ids: Sequence[int] ) -> None: """ Update credentials state to credential_revoked. @@ -318,7 +310,7 @@ async def set_cred_revoked_state( try: async with self._profile.transaction() as txn: rev_rec = await IssuerCredRevRecord.retrieve_by_ids( - txn, rev_reg_id, cred_rev_id, for_update=True + txn, rev_reg_id, str(cred_rev_id), for_update=True ) cred_ex_id = rev_rec.cred_ex_id cred_ex_version = rev_rec.cred_ex_version diff --git a/aries_cloudagent/revocation/routes.py b/aries_cloudagent/revocation/routes.py index 9ac67d782f..88a28a49cf 100644 --- a/aries_cloudagent/revocation/routes.py +++ b/aries_cloudagent/revocation/routes.py @@ -780,16 +780,8 @@ async def update_rev_reg_revoked_state(request: web.BaseRequest): LOGGER.debug(">>> apply_ledger_update_json = %s", apply_ledger_update_json) apply_ledger_update = json.loads(request.query.get("apply_ledger_update", "false")) - rev_reg_record = None genesis_transactions = None async with context.profile.session() as session: - try: - rev_reg_record = await IssuerRevRegRecord.retrieve_by_revoc_reg_id( - session, rev_reg_id - ) - except StorageNotFoundError as err: - raise web.HTTPNotFound(reason=err.roll_up) from err - genesis_transactions = context.settings.get("ledger.genesis_transactions") if not genesis_transactions: ledger_manager = context.injector.inject(BaseMultipleLedgerManager) @@ -820,7 +812,7 @@ async def update_rev_reg_revoked_state(request: web.BaseRequest): recovery_txn, applied_txn, ) = await rev_manager.update_rev_reg_revoked_state( - apply_ledger_update, rev_reg_record, genesis_transactions + rev_reg_id, apply_ledger_update, genesis_transactions ) except ( RevocationManagerError, diff --git a/docker-compose.yml b/docker-compose.yml index 9037211eb1..b25454916b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -20,6 +20,7 @@ services: --auto-provision --genesis-url https://raw.githubusercontent.com/Indicio-tech/indicio-network/main/genesis_files/pool_transactions_testnet_genesis --tails-server-base-url http://tails:6543 + --notify-revocation --log-level debug healthcheck: test: ["CMD-SHELL", "python", "healthcheck.py", "http://localhost:3001/status/live"] @@ -48,6 +49,7 @@ services: --auto-provision --genesis-url https://raw.githubusercontent.com/Indicio-tech/indicio-network/main/genesis_files/pool_transactions_testnet_genesis --tails-server-base-url http://tails:6543 + --monitor-revocation-notification --log-level debug healthcheck: test: ["CMD-SHELL", "python", "healthcheck.py", "http://localhost:3005/status/live"] From 524a89b4c2a68d859648409c906b992acae775ac Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Mon, 12 Jun 2023 13:51:01 -0400 Subject: [PATCH 148/150] feat: implement get revocation list on legacy indy Signed-off-by: Daniel Bluhm --- anoncreds_test.py | 3 ++ .../anoncreds/default/legacy_indy/registry.py | 45 ++++++++++++++++++- 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/anoncreds_test.py b/anoncreds_test.py index 3807e935c9..3dc8d68209 100644 --- a/anoncreds_test.py +++ b/anoncreds_test.py @@ -1,4 +1,5 @@ import os +import time from controller.controller import Controller from controller.protocols import ( @@ -116,6 +117,7 @@ async def main(): result = await alice.post( "/anoncreds/publish-revocations", ) + non_revoked_time = int(time.time()) bob_pres, alice_pres = await indy_present_proof_v2( bob, alice, @@ -128,6 +130,7 @@ async def main(): {"name": "name", "restrictions": [{"cred_def_id": cred_def_id}]}, {"name": "age", "restrictions": [{"cred_def_id": cred_def_id}]}, ], + non_revoked={"from": non_revoked_time, "to": non_revoked_time}, ) print(alice_pres.verified) diff --git a/aries_cloudagent/anoncreds/default/legacy_indy/registry.py b/aries_cloudagent/anoncreds/default/legacy_indy/registry.py index ad3a8c3eb7..ca2e76ff78 100644 --- a/aries_cloudagent/anoncreds/default/legacy_indy/registry.py +++ b/aries_cloudagent/anoncreds/default/legacy_indy/registry.py @@ -451,9 +451,52 @@ async def register_revocation_registry_definition( ) async def get_revocation_list( - self, profile: Profile, revocation_registry_id: str, timestamp: str + self, profile: Profile, rev_reg_def_id: str, timestamp: int ) -> GetRevListResult: """Get a revocation list from the registry.""" + async with profile.session() as session: + multitenant_mgr = session.inject_or(BaseMultitenantManager) + if multitenant_mgr: + ledger_exec_inst = IndyLedgerRequestsExecutor(profile) + else: + ledger_exec_inst = session.inject(IndyLedgerRequestsExecutor) + + ledger_id, ledger = await ledger_exec_inst.get_ledger_for_identifier( + rev_reg_def_id, + txn_record_type=GET_CRED_DEF, + ) + if not ledger: + reason = "No ledger available" + if not profile.settings.get_value("wallet.type"): + reason += ": missing wallet-type?" + raise AnonCredsResolutionError(reason) + + async with ledger: + delta, timestamp = await ledger.get_revoc_reg_delta( + rev_reg_def_id, timestamp_to=timestamp + ) + + if delta is None: + raise AnonCredsObjectNotFound( + f"Revocation list not found for rev reg def: {rev_reg_def_id}", + {"ledger_id": ledger_id}, + ) + + LOGGER.debug("Retrieved delta: %s", delta) + rev_list = RevList( + issuer_id=rev_reg_def_id.split(":")[0], + rev_reg_def_id=rev_reg_def_id, + revocation_list=delta["value"]["revoked"], + current_accumulator=delta["value"]["accum"], + timestamp=timestamp, + ) + result = GetRevListResult( + revocation_list=rev_list, + resolution_metadata={}, + revocation_registry_metadata={}, + ) + + return result async def _revoc_reg_entry_with_fix( self, From d964672cb00d5092a6dc12444ba15929191de06c Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Mon, 12 Jun 2023 23:13:56 -0400 Subject: [PATCH 149/150] fix: revocation errors Signed-off-by: Daniel Bluhm --- anoncreds_test.py | 103 ++++++++++++++++-- .../anoncreds/default/legacy_indy/registry.py | 58 ++++++++-- aries_cloudagent/anoncreds/registry.py | 8 +- aries_cloudagent/anoncreds/verifier.py | 23 ++-- 4 files changed, 159 insertions(+), 33 deletions(-) diff --git a/anoncreds_test.py b/anoncreds_test.py index 3dc8d68209..a433baff76 100644 --- a/anoncreds_test.py +++ b/anoncreds_test.py @@ -104,7 +104,11 @@ async def main(): {"name": "age", "restrictions": [{"cred_def_id": cred_def_id}]}, ], ) - print(alice_pres.verified) + print("Before revocation") + print(alice_pres.verified, "should be true") + before_revoking_time = int(time.time()) + + await asyncio.sleep(5) result = await alice.post( "/anoncreds/revoke", @@ -117,22 +121,103 @@ async def main(): result = await alice.post( "/anoncreds/publish-revocations", ) - non_revoked_time = int(time.time()) + await asyncio.sleep(3) + + # Request proof from holder again after revoking + revoked_time = int(time.time()) bob_pres, alice_pres = await indy_present_proof_v2( bob, alice, bob_conn.connection_id, alice_conn.connection_id, - name="proof-1", - version="0.1", - comment="testing", requested_attributes=[ - {"name": "name", "restrictions": [{"cred_def_id": cred_def_id}]}, - {"name": "age", "restrictions": [{"cred_def_id": cred_def_id}]}, + { + "name": "name", + "restrictions": [{"cred_def_id": cred_def_id}], + } + ], + non_revoked={"from": revoked_time, "to": revoked_time}, + ) + print("Interval after revocation") + print(alice_pres.verified, "should be false") + + # Request proof from holder again after revoking, + # using the interval before cred revoked + # (non_revoked interval/when cred was valid) + bob_pres, alice_pres = await indy_present_proof_v2( + bob, + alice, + bob_conn.connection_id, + alice_conn.connection_id, + requested_attributes=[ + { + "name": "name", + "restrictions": [{"cred_def_id": cred_def_id}], + } + ], + non_revoked={"from": before_revoking_time, "to": before_revoking_time}, + ) + print("Interval before revocation") + print(alice_pres.verified, "should be true") + + # Request proof, no interval + bob_pres, alice_pres = await indy_present_proof_v2( + bob, + alice, + bob_conn.connection_id, + alice_conn.connection_id, + requested_attributes=[ + { + "name": "name", + "restrictions": [{"cred_def_id": cred_def_id}], + } + ], + ) + print("No interval") + print(alice_pres.verified, "should be true") + + # Request proof, using invalid/revoked interval but using + # local non_revoked override (in requsted attrs) + # ("LOCAL"-->requested attrs) + bob_pres, alice_pres = await indy_present_proof_v2( + bob, + alice, + bob_conn.connection_id, + alice_conn.connection_id, + requested_attributes=[ + { + "name": "name", + "restrictions": [{"cred_def_id": cred_def_id}], + "non_revoked": { + "from": before_revoking_time, + "to": before_revoking_time, + }, + } + ], + non_revoked={"from": revoked_time, "to": revoked_time}, + ) + print("Local interval overriding global?") + print(alice_pres.verified, "should be true") + + # Request proof, just local invalid interval + bob_pres, alice_pres = await indy_present_proof_v2( + bob, + alice, + bob_conn.connection_id, + alice_conn.connection_id, + requested_attributes=[ + { + "name": "name", + "restrictions": [{"cred_def_id": cred_def_id}], + "non_revoked": { + "from": revoked_time, + "to": revoked_time, + }, + } ], - non_revoked={"from": non_revoked_time, "to": non_revoked_time}, ) - print(alice_pres.verified) + print("Local interval") + print(alice_pres.verified, "should be false") if __name__ == "__main__": diff --git a/aries_cloudagent/anoncreds/default/legacy_indy/registry.py b/aries_cloudagent/anoncreds/default/legacy_indy/registry.py index ca2e76ff78..e334063b60 100644 --- a/aries_cloudagent/anoncreds/default/legacy_indy/registry.py +++ b/aries_cloudagent/anoncreds/default/legacy_indy/registry.py @@ -3,8 +3,9 @@ import json import logging import re -from typing import Optional, Pattern, Sequence, Tuple +from typing import List, Optional, Pattern, Sequence, Tuple +from ....cache.base import BaseCache from ....config.injection_context import InjectionContext from ....core.profile import Profile from ....ledger.base import BaseLedger @@ -450,6 +451,33 @@ async def register_revocation_registry_definition( revocation_registry_definition_metadata={"seqNo": seq_no}, ) + async def _get_or_fetch_rev_reg_def_max_cred_num( + self, profile: Profile, ledger: BaseLedger, rev_reg_def_id: str + ) -> int: + """Retrieve max cred num for a rev reg def. + + The value is retrieved from cache or from the ledger if necessary. + """ + cache = profile.inject(BaseCache) + cache_key = f"anoncreds::legacy_indy::rev_reg_max_cred_num::{rev_reg_def_id}" + + if cache: + max_cred_num = await cache.get(cache_key) + if max_cred_num: + return max_cred_num + + rev_reg_def = await ledger.get_revoc_reg_def(rev_reg_def_id) + max_cred_num = rev_reg_def["value"]["maxCredNum"] + + if cache: + await cache.set(cache_key, max_cred_num) + + return max_cred_num + + def _indexes_to_bit_array(self, indexes: List[int], size: int) -> List[int]: + """Turn a sequence of indexes into a full state bit array.""" + return [1 if index in indexes else 0 for index in range(1, size + 1)] + async def get_revocation_list( self, profile: Profile, rev_reg_def_id: str, timestamp: int ) -> GetRevListResult: @@ -483,10 +511,19 @@ async def get_revocation_list( ) LOGGER.debug("Retrieved delta: %s", delta) + max_cred_num = await self._get_or_fetch_rev_reg_def_max_cred_num( + profile, ledger, rev_reg_def_id + ) + revocation_list_from_indexes = self._indexes_to_bit_array( + delta["value"]["revoked"], max_cred_num + ) + LOGGER.debug( + "Index list to full state bit array: %s", revocation_list_from_indexes + ) rev_list = RevList( issuer_id=rev_reg_def_id.split(":")[0], rev_reg_def_id=rev_reg_def_id, - revocation_list=delta["value"]["revoked"], + revocation_list=revocation_list_from_indexes, current_accumulator=delta["value"]["accum"], timestamp=timestamp, ) @@ -510,14 +547,15 @@ async def _revoc_reg_entry_with_fix( ledger = profile.inject(BaseLedger) try: - rev_entry_res = await ledger.send_revoc_reg_entry( - rev_list.rev_reg_def_id, - rev_reg_def_type, - entry, - rev_list.issuer_id, - write_ledger=True, - endorser_did=None, - ) + async with ledger: + rev_entry_res = await ledger.send_revoc_reg_entry( + rev_list.rev_reg_def_id, + rev_reg_def_type, + entry, + rev_list.issuer_id, + write_ledger=True, + endorser_did=None, + ) except LedgerTransactionError as err: if "InvalidClientRequest" in err.roll_up: # ... if the ledger write fails (with "InvalidClientRequest") diff --git a/aries_cloudagent/anoncreds/registry.py b/aries_cloudagent/anoncreds/registry.py index 654f257680..486a7e48aa 100644 --- a/aries_cloudagent/anoncreds/registry.py +++ b/aries_cloudagent/anoncreds/registry.py @@ -141,13 +141,11 @@ async def register_revocation_registry_definition( ) async def get_revocation_list( - self, profile: Profile, revocation_registry_id: str, timestamp: int + self, profile: Profile, rev_reg_def_id: str, timestamp: int ) -> GetRevListResult: """Get a revocation list from the registry.""" - resolver = await self._resolver_for_identifier(revocation_registry_id) - return await resolver.get_revocation_list( - profile, revocation_registry_id, timestamp - ) + resolver = await self._resolver_for_identifier(rev_reg_def_id) + return await resolver.get_revocation_list(profile, rev_reg_def_id, timestamp) async def register_revocation_list( self, diff --git a/aries_cloudagent/anoncreds/verifier.py b/aries_cloudagent/anoncreds/verifier.py index eaa29901dc..a0ebbe0042 100644 --- a/aries_cloudagent/anoncreds/verifier.py +++ b/aries_cloudagent/anoncreds/verifier.py @@ -157,14 +157,15 @@ async def check_timestamps( reg_def = rev_reg_defs.get(rev_reg_id) if not reg_def: raise ValueError(f"Missing registry definition for '{rev_reg_id}'") - if "txnTime" not in reg_def: - raise ValueError( - f"Missing txnTime for registry definition '{rev_reg_id}'" - ) - if timestamp < reg_def["txnTime"]: - raise ValueError( - f"Timestamp {timestamp} predates rev reg {rev_reg_id} creation" - ) + # TODO Generic anoncreds rev reg def does not include txn time or similar + # if "txnTime" not in reg_def: + # raise ValueError( + # f"Missing txnTime for registry definition '{rev_reg_id}'" + # ) + # if timestamp < reg_def["txnTime"]: + # raise ValueError( + # f"Timestamp {timestamp} predates rev reg {rev_reg_id} creation" + # ) # timestamp superfluous, missing, or outside non-revocation interval revealed_attrs = pres["requested_proof"].get("revealed_attrs", {}) @@ -484,7 +485,11 @@ async def verify_presentation( schemas, credential_definitions, rev_reg_defs, - rev_lists, + [ + rev_list + for timestamp_to_list in rev_lists.values() + for rev_list in timestamp_to_list.values() + ], ) except AnoncredsError as err: s = str(err) From 3ae503b2dff0cbb851ae5925cb102183ed129abe Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Wed, 21 Jun 2023 10:29:00 -0400 Subject: [PATCH 150/150] fix: missing error info Signed-off-by: Daniel Bluhm --- aries_cloudagent/anoncreds/default/legacy_indy/registry.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/aries_cloudagent/anoncreds/default/legacy_indy/registry.py b/aries_cloudagent/anoncreds/default/legacy_indy/registry.py index e334063b60..1a4f9b2b97 100644 --- a/aries_cloudagent/anoncreds/default/legacy_indy/registry.py +++ b/aries_cloudagent/anoncreds/default/legacy_indy/registry.py @@ -333,11 +333,13 @@ async def register_credential_definition( raise AnonCredsObjectAlreadyExists( f"Credential definition with id {cred_def_id} " "already exists in wallet and on ledger.", + cred_def_id, ) from err else: raise AnonCredsObjectAlreadyExists( f"Credential definition {cred_def_id} is on " - f"ledger but not in wallet {profile.name}" + f"ledger but not in wallet {profile.name}", + cred_def_id, ) from err except (AnonCredsIssuerError, LedgerError) as err: raise AnonCredsRegistrationError( @@ -457,6 +459,8 @@ async def _get_or_fetch_rev_reg_def_max_cred_num( """Retrieve max cred num for a rev reg def. The value is retrieved from cache or from the ledger if necessary. + The issuer could retrieve this value from the wallet but this info + must also be known to the holder. """ cache = profile.inject(BaseCache) cache_key = f"anoncreds::legacy_indy::rev_reg_max_cred_num::{rev_reg_def_id}"