diff --git a/aries_cloudagent/anoncreds/default/legacy_indy/registry.py b/aries_cloudagent/anoncreds/default/legacy_indy/registry.py index 1e3381f742..fa4b062852 100644 --- a/aries_cloudagent/anoncreds/default/legacy_indy/registry.py +++ b/aries_cloudagent/anoncreds/default/legacy_indy/registry.py @@ -117,6 +117,9 @@ def __init__(self): 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.]+$" + # the schema id can be just a number + # (this is how the schema_id is referenced in a cred def) + INDY_SCHEMA_TXN_ID = r"^[0-9.]+$" INDY_CRED_DEF_ID = ( rf"^([{B58}]{{21,22}})" # issuer DID f":3" # cred def id marker @@ -131,7 +134,7 @@ def __init__(self): rf"CL_ACCUM:(.+$)" ) self._supported_identifiers_regex = re.compile( - rf"{INDY_DID}|{INDY_SCHEMA_ID}|{INDY_CRED_DEF_ID}|{INDY_REV_REG_DEF_ID}" + rf"{INDY_DID}|{INDY_SCHEMA_ID}|{INDY_SCHEMA_TXN_ID}|{INDY_CRED_DEF_ID}|{INDY_REV_REG_DEF_ID}" ) @property diff --git a/aries_cloudagent/anoncreds/default/legacy_indy/tests/test_registry.py b/aries_cloudagent/anoncreds/default/legacy_indy/tests/test_registry.py index 4e761fca2d..8c549a13db 100644 --- a/aries_cloudagent/anoncreds/default/legacy_indy/tests/test_registry.py +++ b/aries_cloudagent/anoncreds/default/legacy_indy/tests/test_registry.py @@ -59,6 +59,7 @@ 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_SCHEMA_TXN_ID = r"^[0-9.]+$" INDY_CRED_DEF_ID = ( rf"^([{B58}]{{21,22}})" # issuer DID f":3" # cred def id marker @@ -73,7 +74,7 @@ rf"CL_ACCUM:(.+$)" ) SUPPORTED_ID_REGEX = re.compile( - rf"{INDY_DID}|{INDY_SCHEMA_ID}|{INDY_CRED_DEF_ID}|{INDY_REV_REG_DEF_ID}" + rf"{INDY_DID}|{INDY_SCHEMA_ID}|{INDY_SCHEMA_TXN_ID}|{INDY_CRED_DEF_ID}|{INDY_REV_REG_DEF_ID}" ) TEST_INDY_DID = "WgWxqztrNooG92RXvxSTWv" diff --git a/aries_cloudagent/anoncreds/holder.py b/aries_cloudagent/anoncreds/holder.py index 85fce82b5c..2f6e92e50c 100644 --- a/aries_cloudagent/anoncreds/holder.py +++ b/aries_cloudagent/anoncreds/holder.py @@ -3,8 +3,11 @@ import asyncio import json import logging +from marshmallow import INCLUDE import re from typing import Dict, Optional, Sequence, Tuple, Union +from pyld import jsonld +from pyld.jsonld import JsonLdProcessor from anoncreds import ( AnoncredsError, @@ -14,6 +17,7 @@ Presentation, PresentCredentials, W3cCredential, + W3cPresentation, create_link_secret, ) from aries_askar import AskarError, AskarErrorCode @@ -23,6 +27,10 @@ from ..askar.profile_anon import AskarAnoncredsProfile from ..core.error import BaseError from ..core.profile import Profile +from ..storage.vc_holder.base import VCHolder +from ..storage.vc_holder.vc_record import VCRecord +from ..vc.vc_ld import VerifiableCredential +from ..vc.ld_proofs import DocumentLoader from ..wallet.error import WalletNotFoundError from .error_messages import ANONCREDS_PROFILE_REQUIRED_MSG from .models.anoncreds_cred_def import CredDef @@ -307,7 +315,7 @@ async def store_credential_w3c( try: secret = await self.get_master_secret() cred_w3c = W3cCredential.load(credential_data) - await asyncio.get_event_loop().run_in_executor( + cred_w3c_recvd = await asyncio.get_event_loop().run_in_executor( None, cred_w3c.process, credential_request_metadata, @@ -327,7 +335,7 @@ async def store_credential_w3c( except AnoncredsError as err: raise AnonCredsHolderError("Error processing received credential") from err - return await self._finish_store_credential( + credential_id = await self._finish_store_credential( credential_definition, cred_recvd, credential_request_metadata, @@ -336,6 +344,45 @@ async def store_credential_w3c( rev_reg_def, ) + # also store in W3C format + # create VC record for storage + cred_w3c_recvd_dict = cred_w3c_recvd.to_dict() + cred_w3c_recvd_dict["proof"] = cred_w3c_recvd_dict["proof"][0] + cred_w3c_recvd_vc = VerifiableCredential.deserialize( + cred_w3c_recvd_dict, unknown=INCLUDE + ) + + # Saving expanded type as a cred_tag + document_loader = self.profile.inject(DocumentLoader) + expanded = jsonld.expand( + cred_w3c_recvd_dict, options={"documentLoader": document_loader} + ) + types = JsonLdProcessor.get_values( + expanded[0], + "@type", + ) + + vc_record = VCRecord( + contexts=cred_w3c_recvd_vc.context_urls, + expanded_types=types, + issuer_id=cred_w3c_recvd_vc.issuer_id, + subject_ids=cred_w3c_recvd_vc.credential_subject_ids, + schema_ids=[], # Schemas not supported yet + proof_types=[cred_w3c_recvd_vc.proof.type], + cred_value=cred_w3c_recvd_vc.serialize(), + given_id=cred_w3c_recvd_vc.id, + record_id=credential_id, + cred_tags=None, # Tags should be derived from credential values + ) + + # save credential in storage + async with self.profile.session() as session: + vc_holder = session.inject(VCHolder) + + await vc_holder.store_credential(vc_record) + + return credential_id + async def get_credentials(self, start: int, count: int, wql: dict): """Get credentials stored in the wallet. @@ -384,16 +431,13 @@ async def get_credentials_for_presentation_request_by_referent( extra_query: wql query dict """ - if not referents: referents = ( *presentation_request["requested_attributes"], *presentation_request["requested_predicates"], ) extra_query = extra_query or {} - creds = {} - for reft in referents: names = set() if reft in presentation_request["requested_attributes"]: @@ -646,6 +690,61 @@ def get_rev_state(cred_id: str, detail: dict): return presentation.to_json() + async def create_presentation_w3c( + self, + presentation_request: dict, + requested_credentials_w3c: list, + credentials_w3c_metadata: list, + schemas: Dict[str, AnonCredsSchema], + credential_definitions: Dict[str, CredDef], + rev_states: dict = None, + ) -> dict: + """Get credentials stored in the wallet. + + Args: + presentation_request: Valid indy format presentation request + requested_credentials_w3c: W3C format requested credentials + credentials_w3c_metadata: W3C format credential metadata + schemas: Indy formatted schemas JSON + credential_definitions: Indy formatted credential definitions JSON + rev_states: Indy format revocation states JSON + + """ + present_creds = PresentCredentials() + for idx, cred in enumerate(requested_credentials_w3c): + meta = credentials_w3c_metadata[idx] + rev_state = rev_states.get(meta["rev_reg_id"]) if rev_states else None + for attr in meta["proof_attrs"]: + present_creds.add_attributes( + cred, + attr, + reveal=True, + timestamp=meta.get("timestamp"), + rev_state=rev_state, + ) + + for pred in meta["proof_preds"]: + present_creds.add_predicates( + cred, + pred, + timestamp=meta.get("timestamp"), + rev_state=rev_state, + ) + + try: + secret = await self.get_master_secret() + presentation = W3cPresentation.create( + presentation_request, + present_creds, + secret, + schemas, + credential_definitions, + ) + except AnoncredsError as err: + raise AnonCredsHolderError("Error creating presentation") from err + + return presentation.to_dict() + async def create_revocation_state( self, cred_rev_id: str, diff --git a/aries_cloudagent/anoncreds/tests/mock_objects.py b/aries_cloudagent/anoncreds/tests/mock_objects.py index 0968da95e2..557b6bb74b 100644 --- a/aries_cloudagent/anoncreds/tests/mock_objects.py +++ b/aries_cloudagent/anoncreds/tests/mock_objects.py @@ -30,7 +30,149 @@ }, "requested_predicates": {}, } +CONTEXT = [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/security/data-integrity/v2", + {"@vocab": "https://www.w3.org/ns/credentials/issuer-dependent#"}, +] +MOCK_W3CPRES = { + "@context": CONTEXT, + "type": ["VerifiablePresentation"], + "verifiableCredential": [ + { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/security/data-integrity/v2", + {"@vocab": "https://www.w3.org/ns/credentials/issuer-dependent#"}, + ], + "type": ["VerifiableCredential"], + "issuer": "PuC7nqM4PnFXHmwQXVbuZ7", + "credentialSubject": { + "degree": "Maths", + "name": "Alice Smith", + "birthdate_dateint": True, + }, + "proof": { + "cryptosuite": "anoncreds-2023", + "type": "DataIntegrityProof", + "proofPurpose": "assertionMethod", + "verificationMethod": "PuC7nqM4PnFXHmwQXVbuZ7:3:CL:1255411:faber.agent.degree_schema", + "proofValue": "ukgKDqXNjaGVtYV9pZNkvUHVDN25xTTRQbkZYSG13UVhWYnVaNzoyOmRlZ3JlZSBzY2hlbWE6MTcuMTUuODKrY3JlZF9kZWZfaWTZPVB1QzducU00UG5GWEhtd1FYVmJ1Wjc6MzpDTDoxMjU1NDExOmZhYmVyLmFnZW50LmRlZ3JlZV9zY2hlbWGpc3ViX3Byb29mgq1wcmltYXJ5X3Byb29mgqhlcV9wcm9vZoaucmV2ZWFsZWRfYXR0cnOCpmRlZ3JlZdwAIAEEzIFizO3MnzzMkDFNzK0UG8yCzN7MtEnM28yabszSGCskzMfMi8yoF1xcbSSkbmFtZdwAIMyKzOENzPzMmmnM-X3MzMyRzOHM9RvMzszTzK_M9ELMvFfMqGnM4EEmzL0MzKfMy0rM8sydp2FfcHJpbWXcAQEBNAXM0UxYzKVMzL_My8zoRsykMMyReczHPszZBUXM9cz2XMyizP8zQ8ytCEUQBUjMwTZ_U8yazIlXVMzxd8z6RU8WzJbMlEvMw8z0dMzScsybVQHMrQDMiMyrEj4yzItxBXMdbH4jzKfM38yyzMMta3TM2cyBzJnMpHNnzL85B2xleMyrL8yHUsyQzO_MuczuJzdfEcy9zMtXDWrMjszvATE1zPnMr3_MnjrM0QrMhszqzNxAB8y6S1TM4cyVdSLMgcy9V8y4zMsGzPd6UCHMj8ypzJMsR8z0zJbM6cySzKUrGj1vKMzqzJJtIMy5KgA_f8y8IsyuzJ3MzsyFzIJHIMzAD8zdf8zXzLXM9sz7zNhMzO1xLczKzJRiZFhRZszlzPMSYcz8zK0RzIELOlHM4czazPJLCcydzMzM1sybfw_MqMzezK7MhCdzzL3MpkTMnMyFzKMaIszkzLFSzL9wzKvM-TE1zNg8zK7M0wE1zLFjK8zwUg5nNmehZdwAOczMzPvM9sz_zMrMnsyUEMzSTczsXcyfW8yEzIfM-szdNszgzM8pZTNFzNdozIfM0G7My8z3zJTM6U0YzN5KDh4sXMz5zLbM2mvM1sy3zKrM3HEENWV8cV6hdtwBfwY4zJt-zPEZCMzWWh9OzIvMt0DMtEANzIPM9My7zKPMyXFizNEHPMz6zLhkzOkYzLvM08zMzIN7zK4nPEfM0XEOzKfMrGYEzLPMn0nM8Mz6zIkdzO8Uc8z5zPTMzMzeSMyMzJBOzPVGUcyvVMyDzIzMtCTM3sygzNA1zNHM48y5KsynZ1B-MUrM1y1uW8ysPTRCzO1qD8zxL2LMtSBLQsyPzLoKL8y9zLZLzKPMgA1lzLhJzOBlYDMTJ8yZzM8_d8zSzJY3CsyRzO9yW8y0OMylzPpnMyjM78ysKcy5zMpFGBkvEsy9zMcBzIXMmyMPzJzMyMyrSsz5zJbM0UPMm0rM2Mz3MMyMzOUSLFvM18zHYg1yzJ4oGsyYzPvMpMzqzMzMhGAQKcz9Q8yxzKrMiczcfnLMh8zBTlfMtsyrzO0lzPzM_8ydzNLMiWshzLLM9sz5RMzzTTwCZMyszOcMIgbMzELMzMygfSHM38z0zN8dzPjM2nx1zNDM7syzdx7MxX0ZzMcfT2w9zP7MhkPMuyXMmmjM8HITfSzM4xQVzPvM1sy6ESLM6czMFczEdcy1zLLM81lkc8z6zPbMs03M-MyoZsy7zLFLzPXMjcyUHi4QSSvM3syEzNrM3cyZG8y2ScziahxPzLsGzLgDzIUTAMz9WsyOzJLMsx52zIMazPcuzPxZzPVFzKrM7AsGWkZbHczczLMJzIlyzPZyf8zazJNvZszWzK5tHcyaEXEqTybM-SfMiHLM_XTM30ceoW2EsWJpcnRoZGF0ZV9kYXRlaW503ABKV8ypzOd_zJQDacynzM3MqsybzIVOHUHMhhrMiAjM9joNzP1OzNN2zMd9zMw7ZF5rzLQoDXJnzMgYOhLMlszJzK0GJcyjbBLMhnorJRgpfcz8zO_M6BrM8czrJnQQZ1kazJPM2ksafql0aW1lc3RhbXDcAEojzN3MrlsEzLDM6hDMym8nzOzMkszZYszDzKk1zNFsE2nMscziWMzMGMzJQTnM08zJzOVcfjoyzNHMwGBoBmTMryobTS7M2czbzMrMhszxzPHMssz3zJrMtcztUczbH8zwXmnM1cypMy_M4h_Mk8yGBa1tYXN0ZXJfc2VjcmV03ABKzIEoEDAyzOPM5WpoLXohQX8NzJ3MscyfDcz0AkU9O8yjUTPM9EPM78zxzInMj8yHbjtmf8zUzOLM_8zmb8yOJGzM5jvM8ULMgmvMgDUYFsztO1F2zO3Mq8zoa8yuGGfMh8zLc8ztTBdhpGRhdGXcAEoazLLMrwvMtzI9zPJIzJPMrlIPRMyaOxPMvcyqL8ytzIbMlCbM7MyFUUpla8ywzM_MpMyiO8yjzJphzLzMlczXzJQFGGXM2zozYsyNzNDMuxvM5yvM_MyeC8yqQ8zAzMDMoV_M-szINzrMtHFzAczAzK6ibTLcATBFzN3MhVYpzOICzKM5Iw0DzL3M98yKzOjM38yQZFMKzN0XzKZRRMy6L8zXfMyjzI_M8MzUH1_Mui_MykvM38yLYhrMosz2zN9KzO3M0MzsY3YdSsy8zJrMs8zvzNfMyszGICrMx3nM1czyNCXMsVbMvMyYecyfc8zKLczzKQwFQispOhMSCRASzJIgzPcLzOHMvsyAS8zsdnvM_jDM0TUhPCHM7cz8zLodzPFMa8yFMAwzzMNnzORqzIHMuRfMxMzlWSgmQ3TM8F_MuTDMgszozOhScGXMnxoRTzZBzMZ2FCQIzO_MtEjMx8y2zN_Mp8z9zPHMu8zIzPsOzOTMhMzTzIjMrszSc0PMjMy4XxZ1BzfMkMzfQMzoVwkozNUjzOFFzI_MmH_Mo8yAzPPM9BrMhcyoOcyBfsybMQoOzJjM7MymKcy1bmPM1x7Mv8zyzNnM8szFzJTMhxZpCszKzKB9EFYKzIASzJEATMzkzNBwzKjMk3lrdsyXEszYNsyTOX_M5sz2zMHMu8zozJnMucyLzMIezJ_Mysz8XTzM2GxKzN_M7szzzJxdzO3MuUPMnx3MzczmzPfMw1rMl8ydY3vMoszQUsyvGczGzLnMhszqJalnZV9wcm9vZnORhqF1hKEy3ABKzObMoyvMoS0qIVgCzKA-zLDMmRfMj8zqzOTM2cy6WMzFzOzM8cy6YCElPiwjN2s6zIVLzKc1YnAEzJx9zJtjzMxhzLDM8szfF13M9MzUPz54dczqzM1uMhPM8VprzIzMjcygCcyYQVMBE6Ew3ABKzNjMowLMxUPMxcymzPzM_szcUszazPRazO05Rn7Mq8zRS8yrX8yvHsydzK3M1czWLCbM_sydzKMTG8ypzLQ5JTDM3Myac8yZEczqzLDM0mXM6MyvzM7M0sz9zP_M-BXM-lfMs8y8zKxiXQPMmzXMxMzBZxoPzNyhM9wASszKNlfM5ygAAyRszIvM7BFAKVrMiMzQJczZGMzOXszxzJkZzLTMo8yPeE3MnMywzMbMtMycMTghCFMeHzLMqWrM7WY8CcyZEczfBcydDkExzLHM-sy6zKV1H8yubsynbGTMw8yhzIFvzPMuoTHcAErMsmZgzJvMuGvM8S_MvTXMuMyWzMbM4MylzPpzBTjMhcywzO5szIFUzKlYzLDMsMzlUHfMhAbM5MzLITUfecyYARzM-8zeH8yWzNDMn8ySZ0bMiczKzMrMkSPMxMzbAzcDEH3MyszsF1bMxDgpI8zFcqFyhaEx3AEpzJDM-mZYHcysKcyFzIvMzGXMginMqsz5Ysz_PsyYLGoQzLV3cj7MrsyczOzM8iJRzJ7MjcygUczCzIHM6GLMvszKQczVzK_MlgnMgsyPzKnM5UFOEMyfzL_M1iIdzNsRzK_MyFjMj2A4OyxNLUXMmcy7bCsVaUDMzcy0zLFWZ8y-RszEKVMHzNZ6RczezLfMscz-zJjMo2V5zJXMqnAXd3t8zKrM6MypzO3M6B8nBsyZahJMNcy3dMz_UXLM1szvzM08zL_M0MzuEBhHzIVazPF9dsyUA8z5zO_MpgHM-szOIzF9acyeRAfM-8y0fkEmzLUuYsy_aczSzNvM9ClkzPbM_sytzKNKzNjM0cykQcz3XC54zLLMzwwWzNEyzLjM5MzfzLnMpMyyRMyJJszrzILMuULM-BPMmU1qzKjMmgdRAnskzNcdzIdEzNxLSHXMuxPMlczBzLnMvlLMkQfMpMzqaWNHzJp0Y8y2TsyhGzdAzPE7X8yLzNLMwxMzzNvMv8yIzJvM3sy9zOIJR8y4AczUzNlyeMzyzL12zOoIzPkWzPgkzOhlHWDM2MzGzN1fzNPMy8yizOfMi8zjzMgUzJUWbaEw3AEqHcyMzPlYzNh_BszqzP0wzNF5zOgwzOIxzPLMrEQfCcz6AMyrzKBfzNBwX1PMwh5xCnoDzIlQQ8zkNh7Mz8yiDMyDzPXM3czPbwZyzJ9kEQpCY3zM38yQzMNEJibM_TcUFjrMycyXBXQwUGPMrSMfzN52zNd1zOkWzKLMnj7M1xx0zJ1mzP1BzNgEzJnMjczrzINNzO3MmnbM-cz0zLnMvcyGzJ5PQ8zOzKJ8ZA9MzKVqDDZrzNMFzMvMz8zQacyXIg3M5czIN8zDzLnM6A_Mhcy4zKJXEysfXDQePR5KN0wDzNfMmFBnW8yNzNzM-nfMpEo0dMzEQsz9C8z_zKHMsMyGU8yxZxgNzI3MjszTzJIZzJHMx1wQzPQMzP1ZJBPM78zaTH1LUhRJcFVvXC5tzOFbzOvM-QQzSSXMvcyDc8zIzJhURiPM6H_M91HMqFfMyczrzIhgdwYeIMzSSAAazLFxzNvMzTLMxczpzObM6R8FfDHM4U41zIg3zIHM6gVDzLpYzLDM-MzUA8y5HnbMt8yaWllZzOoDQ8ySzKrM58z7zI8izN_M5QsvzJ3M1MzIoTLcASp_FALM48ymIcyKzMs_zPR9zODMqMzZzIPMicyRJcygb0PM7szqRGfMl0wHc8yJzLwkzK_M-sy0c8yLzILMmigHzOjM7WgqzI_M1syrN0nM18zhzPHMl8zPDF4VMczEzM9UzIzM5EnM_czjzPtycmXM0mnMtGMOQAwtzKEDzPVBFDPM1czezOvMgSzMiczcYMzwC8zLzI0rzOJTzJ7MnMyhKcyozNDMncyQT8yszPh9zIXMlhDMqgbM88yAzL7MhUnMlFrM2n3MiCtVQMy0zLQFzNV0AMzWzIvMncy0MFzMkszXUMzHzKMGzO0QzM0SdypZzMUPzNU8MmxZzJvMimZ_zNTM0V0JeMzTdcylTMzQUszIzOZXQHlALBbM_W7MvigpzOPM-yLMoMynVMyLzJVzIjcTFTzMp8zjNMzxzMHMosyrVWRZAcyHzJ0ezM98zKfMtwx9zIM2zOHMgczcTWbMlcyWdMysFcz5d8zkKsz8zPJTb8z3zLfMw8z1EULMlMyJRRDM-U0uzOvM9ArMlkjMgszfCnHM2sz6E8yuSszpRFh1zJMzAS7MnSnMvXfM88yfK1M9zKYKFg_MozDM8QAvTaVERUxUQdwBKkfM08yZAARwRsytzNLMowkkHWZsd1rMqifM2syIzIPM4szoLi5BSVIYSW_MqifMhsyxzOzMzEYKzJXM1XbM5My8S8zazL8dzPMcTsyXQhXM3syGzOonzPc_FczXzLV6zMrM9VrM8czuYszFNlTMtcyHzI9nzJUzcczqzK0xVcyCzJPM8T9DC8yoFjFgNcyAzLDMuEbMogDMkX7Mw8z3zLHM8gzMlF7MjczSzOPM1cytZmJkOsyuQVlhzJB5OGHMiSg8KyfMp8zczOXMgx0dzP7M_czLGMyMzPxKYcyzzJ8lzJRXBh_Ml8y9zNLMkMyLA27M5krMixR8azIPXF_MoMy7Z8z6zJI_zJkPVszBZczEzPvMln91zKTMtX4DHczsSRJ2dcyqaj8wzMjMwAclRMz4zL7M7T7MiMz7zIHMycyRzJTM3hrM0BjMjmkgNXpDzMshzNvMlMzkzJnMvVXMi8z5zLEASGrMsszRzPvMrczUzMpWzLXM93cRGSZwzPHM2szoWMyQScyoaitwzPEBV19YzN8xzJvM4CFAOMzQQszdzMpid8zuzLbMpczjzMI6ZsyQfVhgzJdDAGTMgqEz3AEqJcyKzL3MusyMzIUBW0fM9whtzJHMiXbMkGPMrszMWMzKGcyaeczhV8yLzNVODMzzXsynbw_Mjh3M98yUWcyVzNxdbkjMyVFCOMyaV28tGszzzMjMiMzAGcz5zObMlD3MgMzczLnMuDxRUszXRHTMpmDM2iNuzMJkzOgAM3pXNMygD8yzG8yGdmfMyszVzODM8UvM8i3Mz8z3UXMwGMzHzOl7HsztI2LM1yUuKczpe8ySzPJszN3MjxI9Zsz_zIkoXVnM8czlzNLM0cy6zO8WzK9VzOHMszEVzMPMp8zjzPASZsylYsy7zITM8iNJAGfMnMyGWczRck3Mm8zszKfM5y51EMziccz3zPrM7VZCzIE9XFDM8szaVMyVPSYnTkR2NXUbzII0zKczD8z3zIvMpBTM5z3MtzbMpMywCWzM4wfM8szfLsyraMyjGjQPzKnMoyouzKlpzODM_HrM_8z6Q0lJzOoaFsyUzIBuRV0mzP9MzPRBZik7YTlpRMyxzKJwPsy5zPrM6cy7DntDOMzTzIp7zMjMqmlUzKTM7g7MsszwJ0nM5cyueDrM53_MrcyjzLZKzPYSzKGibWrcAEpXzKnM53_MlANpzKfMzcyqzJvMhU4dQcyGGsyICMz2Og3M_U7M03bMx33MzDtkXmvMtCgNcmfMyBg6EsyWzMnMrQYlzKNsEsyGeislGCl9zPzM78zoGszxzOsmdBBnWRrMk8zaSxp-pWFscGhh3AFdBsyVzKzMp3_Mlik1zNfM-iVSzIhczLzM8jpCzM1vzMjM6szJLmnMnMykzN0UzPzMmW4PzPnMzsy1zL5pXsyUzN_MwMyjKy11ZhnMwWvM98zWzIDM9cz_zJvM510IJ8zwPczTXGU0zMhSbMyrzOUNB27M9yHMtkZgzKJazLLMz8z3Rsz7YMyXWcyzJTHM9ULM03HMqczVzNt9zKfM7MyMzNXMiwPMvMyTzJ_MoRQezKHMyszWzOIjzJbMsczazP_MrkvM_MyhzPPM9MyeZ8zvzMRgzLzM0MzSRRNLzKrMo8zmNsyWPypwBcymM0NazLREAU0RzJ_M3czSISvMzsz7zOPM7MzqG8ypbczjzNLMzj3MmMzzFGjMlx7M507M5GTMz8yRzLQ_zPhbzPE0zLJWEMyJzNXMvC7MmnrMh1LM5sywZAXMg8yPag9kzInM7My5AhRsIMyyzI_MvszjzPDM6czQzLnM03_M5czxQkLMgczPJczgOy7M-y7M1syEEMy6zPXM4gNNzJTMm8yrSczbOCBCI8yszP3Mx8ydd8zBzLIbzMHMm1Z6zOMSzO0WRhZgLw3M8sybJczRzPUgR8zSzLzM8RUezP7M9kJhJBvMtMyEWzwDzI0czLjM-T52zMPM9RnMriTMhMzAEg5TzPvMrhZ-zMrM1cyGIcySzPU0RiIXJBtbzLNszM_Mu1gDzOByzKPMo8zXzL_Mpcy_zLmhdIWlREVMVEHcAQECFczszJbMpWzM-UcLzLkQPz_MuWkwzKRYU08jQnHMnsyWzLZazKzMtWsFSUXMwMynzNMGzOJczL3M-MyFzLo1BMzxzMJ8zJoPDsyfzM5wLMzrLcy_zPfMjszuzLvMmszmzMFPesyfK8y0DszjIBViT8yxzME4AHcPPWpvzLAazPVzDjAlzJrM-szdzMLMwszaL2LM78yvT8zMZMzDZndnKkXMisyzQGIuSczwzIzM2MzwC14ZNB3MkMyIP1cozOfM6WNBzN_Mux4UCB19zKsMzKUzzLQgzNrM2FcqzKMcMF0jP8ydzMccYMzANczsCMzczO7M9szccczBfMzLe3bMzMymzJbMhVVKzO0FzL_Mq8z6FRMNzOnM0CDM-cyIzNZdzNfMiczpUUDM8BPMkyHM8syyRHt6zN0mzK7MmAQEzKTMjh4xA8yCFi1SzKpczOnM0cz1c8zqzKIrVStvzOrMvcycA0kQzIESzIrM9Mz7zMAqJ8zHGGHM_cyAoTHcAQBYOTLMn8yrLSrM-8zszLzMlsz4fMy7DczIzNgzOsz5OHMnYzDM7103zOPMhcyBEX8QM8z4zP_MjyrMihvMl8yrzOBofTbMgcyfzJE2RkpVzOo8zPl_zIzMpsy6zP0bzI3Mj8zCzN_Mksz7zJfM0MzGFFzMvDsEzMPMr8znzOFCzLXMrRrMjcy2b8zBzKDMuRnM1cyBzOkxV8yOzMLM5szGBRUTzJjMzczqzITM6cybaX90XczYcczgVgPMpWPMo8ypzMLM0jLMjzHM0cz3OXTMiMygzIREzODMr2rMzcyUzJTMrsz_cMzczMokzJAqEszNfMzYe8yyzOhDzOzMp8y9Lx_Mx1_MlsyxI2rM7UUezMrMuBVRzJg2zNzMvyHMvUzMh2gwQcyWzNvMxMy-IMygzO3MuWzMzhttc8yhzL3M4C9DY3vM5ShazPBCRcypeczCzLzMw0TM9S3MnMyoYszKzNwmzJbMnszbfhtgzPDM0szFNcy7PsyvzLzMhsz7DcywzO3MiszjzKHMwsydzPzM5HIZXKEz3AEBAhkQV8ygzOXM6D_MimzMqMzFchZQzPzM5mw_zL1zT8zyzJgVzJTMsGpJzMp5zPXMykpcI8zMIEHM3syBJnDMgMzPzIcjzKrMiMzNzODMhSXMvczzDQHMxz0hzMfMsMznzN85cCbMzg7MrsyQcXIazLw4zJzMyh9-YQdYAcy3zI8DC1_MjHHMgwIrzOkvzJvM9UjM3czOzOHM3czfzKrMyMy1zKZYzJVlzOwJHczqbF5fzPbM7czoR8zGzJPMmH94VlrMrFJWzI3M3MyQzOAnECXMgMy8Acz2zJfM18zDzOrM1DTMnWrMqjASSsySI2fMuyhYD8ysUXPMzQrMqQ3MuEBtzNFNCMzLzN_Mu8zTzKQDzP5eTczJHCo2J8znLyLMyczvKBBKzMwkWz8XzOfMiMyazK4RcjnMyczlzLrMrMztzOYMNmoFzPxlzKx-zOQ7QRvMy8zczNswzI_Mq8zWYW_M7xUCJTTMyCV2OUYXzMVHzIAvzNHMlH_Mi8yZzOyhMtwBAQHM_sy7IE7MyMzfzJXM93A-zOpYITTMucyWEszczOXM_MzazLLM5DvMzlhzzNvMlczrzKjMtgfMkczmNcy2zJvMw8zLAGzMrn7MiHTM_zPM-8zdQ3_M-8zQzKrMw1PMtigxzOjM88y3zMBgUivMgGQUzNg7zI4NL8zzzK8MDMzQzLjMi8ymzIBnHcySKsyuNcyHISrM9iRgPMyvBTZZzO_M8MzKfcyRbMyqWcyTzIRQzLrMlMyazPxzzKHM_Mz2ZEZ1zLxAzN4ob2jMpczJzI3M8cyRzP5OzO4qzLEdNSU5zIPMt2PMh2PMpwEIzNFbBFzMx0h_PUk-zMI4zMvMqCbM3szlXADMv8z1zLHM-My7FH_MyczYzJjMtsyMWx5UzMRFA8zdMWPMiFkSE8z0zI8Le8zDzPrM6czpzP9fOBDM2yLMslErHFrM5My-McyQWFzM7RwoQnA6zNkezL3Mx1LMihLMjC9IbMz7IszzIDwzKMyVzNlnUcyhzLAyFgbMr0bM66Ew3AEAzNYTzIDMncyQzOHMjcymGGDM_n7MlMzZPxPMow7M6cyRVyAybsyQGMzXGXpaPAXM9MyQLMz4zOPMt8zxzKnMnTlAHsylzOhozIjMtsz5V8yqZRDM9MyJWmxzzIQmzK1xTGhXdmdWzJbMmsyJzJ_MkMyKS3rM5My5EHFnVszVzKdIzNpIfMyizMPM8MyWUMzEBMySdGsjAXDMgsyKCszWUwPM0EXM5czyHkhdFMyeQRDMgsyIUsyUzMXM_czszNfM48ynzLXM93RNXMyozIYPzINaGD1XzKAJZ2g1zO_MwMzMWTvMiRnM-HXMg0oJGsy9zKLMuWwweMzvfj15zObM7F7MpMzwCszdzKJnb8yeMkjMr8zlzNhDaX5wOsyqZArMxszuzJcrzMjMtsybISLM4szVzMNxT39yKmNZHgtNzNXM3Mz9SszURG8PzNFOCcz0CEnMiV_Mwsz6S8yPQHAvacyPzLktQszfLCHMnszVzPfM-8ynW8zVzNZ4qXByZWRpY2F0ZYOpYXR0cl9uYW1lsWJpcnRoZGF0ZV9kYXRlaW50pnBfdHlwZaJMRaV2YWx1Zc4BMhonr25vbl9yZXZvY19wcm9vZsA", + }, + "issuanceDate": "2024-07-11T14:33:48.869474016Z", + } + ], + "proof": { + "cryptosuite": "anoncreds-2023", + "type": "DataIntegrityProof", + "proofPurpose": "authentication", + "verificationMethod": "PuC7nqM4PnFXHmwQXVbuZ7:3:CL:1255411:faber.agent.degree_schema", + "proofValue": "ukgOBqmFnZ3JlZ2F0ZWSCpmNfaGFzaNwAIMyBzNUrzIpQWMzSzOjMhiPMhjgYzL5ZzLUZL8zzzJI0zJB1M8zHzJnMnVBteQnM6qZjX2xpc3SW3AEBATQFzNFMWMylTMy_zMvM6EbMpDDMkXnMxz7M2QVFzPXM9lzMosz_M0PMrQhFEAVIzME2f1PMmsyJV1TM8XfM-kVPFsyWzJRLzMPM9HTM0nLMm1UBzK0AzIjMqxI-MsyLcQVzHWx-I8ynzN_MsszDLWt0zNnMgcyZzKRzZ8y_OQdsZXjMqy_Mh1LMkMzvzLnM7ic3XxHMvczLVw1qzI7M7wExNcz5zK9_zJ46zNEKzIbM6szcQAfMuktUzOHMlXUizIHMvVfMuMzLBsz3elAhzI_MqcyTLEfM9MyWzOnMksylKxo9byjM6sySbSDMuSoAP3_MvCLMrsydzM7MhcyCRyDMwA_M3X_M18y1zPbM-8zYTMztcS3MysyUYmRYUWbM5czzEmHM_MytEcyBCzpRzOHM2szySwnMnczMzNbMm38PzKjM3syuzIQnc8y9zKZEzJzMhcyjGiLM5MyxUsy_cMyrzPkxNczYPMyuzNMBNcyxYyvM8FIOZzZn3AEAzNYTzIDMncyQzOHMjcymGGDM_n7MlMzZPxPMow7M6cyRVyAybsyQGMzXGXpaPAXM9MyQLMz4zOPMt8zxzKnMnTlAHsylzOhozIjMtsz5V8yqZRDM9MyJWmxzzIQmzK1xTGhXdmdWzJbMmsyJzJ_MkMyKS3rM5My5EHFnVszVzKdIzNpIfMyizMPM8MyWUMzEBMySdGsjAXDMgsyKCszWUwPM0EXM5czyHkhdFMyeQRDMgsyIUsyUzMXM_czszNfM48ynzLXM93RNXMyozIYPzINaGD1XzKAJZ2g1zO_MwMzMWTvMiRnM-HXMg0oJGsy9zKLMuWwweMzvfj15zObM7F7MpMzwCszdzKJnb8yeMkjMr8zlzNhDaX5wOsyqZArMxszuzJcrzMjMtsybISLM4szVzMNxT39yKmNZHgtNzNXM3Mz9SszURG8PzNFOCcz0CEnMiV_Mwsz6S8yPQHAvacyPzLktQszfLCHMnszVzPfM-8ynW8zVzNZ43AEAWDkyzJ_Mqy0qzPvM7My8zJbM-HzMuw3MyMzYMzrM-ThzJ2MwzO9dN8zjzIXMgRF_EDPM-Mz_zI8qzIobzJfMq8zgaH02zIHMn8yRNkZKVczqPMz5f8yMzKbMusz9G8yNzI_MwszfzJLM-8yXzNDMxhRczLw7BMzDzK_M58zhQsy1zK0azI3Mtm_MwcygzLkZzNXMgczpMVfMjszCzObMxgUVE8yYzM3M6syEzOnMm2l_dF3M2HHM4FYDzKVjzKPMqczCzNIyzI8xzNHM9zl0zIjMoMyERMzgzK9qzM3MlMyUzK7M_3DM3MzKJMyQKhLMzXzM2HvMsszoQ8zszKfMvS8fzMdfzJbMsSNqzO1FHszKzLgVUcyYNszczL8hzL1MzIdoMEHMlszbzMTMviDMoMztzLlszM4bbXPMocy9zOAvQ2N7zOUoWszwQkXMqXnMwsy8zMNEzPUtzJzMqGLMyszcJsyWzJ7M234bYMzwzNLMxTXMuz7Mr8y8zIbM-w3MsMztzIrM48yhzMLMncz8zORyGVzcAQEBzP7MuyBOzMjM38yVzPdwPszqWCE0zLnMlhLM3MzlzPzM2syyzOQ7zM5Yc8zbzJXM68yozLYHzJHM5jXMtsybzMPMywBszK5-zIh0zP8zzPvM3UN_zPvM0MyqzMNTzLYoMczozPPMt8zAYFIrzIBkFMzYO8yODS_M88yvDAzM0My4zIvMpsyAZx3MkirMrjXMhyEqzPYkYDzMrwU2WczvzPDMyn3MkWzMqlnMk8yEUMy6zJTMmsz8c8yhzPzM9mRGdcy8QMzeKG9ozKXMycyNzPHMkcz-TszuKsyxHTUlOcyDzLdjzIdjzKcBCMzRWwRczMdIfz1JPszCOMzLzKgmzN7M5VwAzL_M9cyxzPjMuxR_zMnM2MyYzLbMjFseVMzERQPM3TFjzIhZEhPM9MyPC3vMw8z6zOnM6cz_XzgQzNsizLJRKxxazOTMvjHMkFhczO0cKEJwOszZHsy9zMdSzIoSzIwvSGzM-yLM8yA8MyjMlczZZ1HMocywMhYGzK9GzOvcAQECGRBXzKDM5czoP8yKbMyozMVyFlDM_MzmbD_MvXNPzPLMmBXMlMywaknMynnM9czKSlwjzMwgQczezIEmcMyAzM_MhyPMqsyIzM3M4MyFJcy9zPMNAczHPSHMx8ywzOfM3zlwJszODsyuzJBxchrMvDjMnMzKH35hB1gBzLfMjwMLX8yMccyDAivM6S_Mm8z1SMzdzM7M4czdzN_MqszIzLXMpljMlWXM7AkdzOpsXl_M9sztzOhHzMbMk8yYf3hWWsysUlbMjczczJDM4CcQJcyAzLwBzPbMl8zXzMPM6szUNMydasyqMBJKzJIjZ8y7KFgPzKxRc8zNCsypDcy4QG3M0U0IzMvM38y7zNPMpAPM_l5NzMkcKjYnzOcvIszJzO8oEErMzCRbPxfM58yIzJrMrhFyOczJzOXMusyszO3M5gw2agXM_GXMrH7M5DtBG8zLzNzM2zDMj8yrzNZhb8zvFQIlNMzIJXY5RhfMxUfMgC_M0cyUf8yLzJnM7NwBAQIVzOzMlsylbMz5RwvMuRA_P8y5aTDMpFhTTyNCccyezJbMtlrMrMy1awVJRczAzKfM0wbM4lzMvcz4zIXMujUEzPHMwnzMmg8OzJ_MznAszOstzL_M98yOzO7Mu8yazObMwU96zJ8rzLQOzOMgFWJPzLHMwTgAdw89am_MsBrM9XMOMCXMmsz6zN3MwszCzNovYszvzK9PzMxkzMNmd2cqRcyKzLNAYi5JzPDMjMzYzPALXhk0HcyQzIg_VyjM58zpY0HM38y7HhQIHX3MqwzMpTPMtCDM2szYVyrMoxwwXSM_zJ3MxxxgzMA1zOwIzNzM7sz2zNxxzMF8zMt7dszMzKbMlsyFVUrM7QXMv8yrzPoVEw3M6czQIMz5zIjM1l3M18yJzOlRQMzwE8yTIczyzLJEe3rM3SbMrsyYBATMpMyOHjEDzIIWLVLMqlzM6czRzPVzzOrMoitVK2_M6sy9zJwDSRDMgRLMisz0zPvMwConzMcYYcz9zIA", + "challenge": "10275921379803826423", + }, +} +W3C_CRED_DEF_ID = "DyZ6N8yDTQsdJazNGRG1Wa:3:CL:1255405:faber.agent.degree_schema" +MOCK_W3C_CRED = { + "@context": CONTEXT, + "type": ["VerifiableCredential"], + "issuer": "DyZ6N8yDTQsdJazNGRG1Wa", + "credentialSubject": { + "name": "Alice Smith", + "birthdate_dateint": 20000711, + "timestamp": 1720707393, + "date": "2018-05-28", + "degree": "Maths", + }, + "proof": [ + { + "cryptosuite": "anoncreds-2023", + "type": "DataIntegrityProof", + "proofPurpose": "assertionMethod", + "verificationMethod": W3C_CRED_DEF_ID, + "proofValue": "ukgGEqXNjaGVtYV9pZNkvRHlaNk44eURUUXNkSmF6TkdSRzFXYToyOmRlZ3JlZSBzY2hlbWE6OTQuNDYuNzGrY3JlZF9kZWZfaWTZPUR5WjZOOHlEVFFzZEphek5HUkcxV2E6MzpDTDoxMjU1NDA1OmZhYmVyLmFnZW50LmRlZ3JlZV9zY2hlbWGpc2lnbmF0dXJlgqxwX2NyZWRlbnRpYWyEo21fMtwAIC3M5nrMyTjMr0bM6syCRRgDB8z4TMzdzOrMucy5zKPMisy6zJxTzPB4bVHMszJEzIahYdwBAHTMiCVWzN_M98zRzPPMgkVezLDMlMz8fnN8UcyFzJXMigNmzMLMwiPMsczEJcz1zN7M0cyxzK7MwljMg2IqdVrMy8yHzM3M_mNpBMz9XF1SzPVIzLEmzPbM1BjMyHzM43zMkMzuEMznzKBkJ8zadMz8zMLM_CBqE2fMtRjMuczDzIR4SRhQSRbMhUrM1gHM18yUzP7MsszJFszqJRzMnczxCMzazO4LBcyIDMzEzLRmScyYzLXMgMyIzKHMnmBTzIvMysyHzIZ0AsyEzMvMmMz_SczrLczAzPhsecySzP3MysynzMzM0l0STcyPcszWzKFJOQUAzLbM5cybzLV7zOHM-Xw6zMdZzNEOzKlwZcyezOolV8yqzOIqQ3LMkMzmQczWzLELFcz0D8zRWVXMuMyHEszdF8z4REknzJvM7MyVzP52zPjMnkDMwyAmFg_MzgfM32TM7sy6zKZQzPhazJnMuCrMwGdpzIPM0szYzPZ1zPgMHTnM9MzozLJTzMIwzP7M0czwbkLMqh7MyqFl3ABLEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALynM23zM3iFzAcy0IDwJUczoJ6F23AFVCcytzP_MusyjzKXMnRXMzcyOzI5wDS5VOMy0dMyVH8zXzJ_M-cyIASNgJns2zPfMzzLMlW1MU8zgBHlMzP0AzKvMssyYzLHM_U5vzJnMnmrM4DfM91k4PBIIVMzFzKl4F0bMmR8XzJfMxU7M2sz0Bcz_GMy2zMFEJsyybgwbzPZsaTPMlQcSzKY6eczbbTRZTMzyzIzMmxxTzKgKzMnM0czuHWjM9My0c8zKzMU9cEfMySXM98zbPw7MsHlyzOTM6lLMucyQzJjM7syHQQPMtUrMgczOaD7MlXp4SszXNcyFzLDMgGRxCczCClxHAVXM-goMLsyLY8yZK8yHzMY3zKVXKwp2zLjM4MzwzLpvUcyfzMYYAQAsG8zlzP0MMHwHJAfMgAPMtQDMhCsLzNjM1cy8B3fM6szVDczuYMy4zILMo0MizJgczOp8zLUFzLExzNPMkBMKzKbMhczLL8z6Tcy-dMzXRGXMnF0tR8yQzLVOTwXMp8yeC8yrdnnM9Myeb8zVPg8rzO9WzOLM0cy2zMtkBszjJszozNHMmgZhzOsHzNRSzLbMiszTJszbzMDMrszkLMyhzLJ4aRYnVw7M3cygzNxHzIhDSk_MpVPM-MzmzLkAVMyzzJMjID_MvEjMisz5zOnMy8zSFMzMbUXMu8zjzMBCUESscl9jcmVkZW50aWFswLtzaWduYXR1cmVfY29ycmVjdG5lc3NfcHJvb2aConNl3AEAzJBUX8zkPVXMwMyhzPwxdWrM58z6JMz3zLDMlVHMg8yvzJYOWcyKzJZozPk1zPcJP1ZZzK3MxDFJzOwyF0rMvC9xzP0XzNXMyH7MgMy4CMywzPgkzJPMy8zQzMhwMkvM4cz2zPYXzJMlzJXMnMyZzM00zMjM2lITzKFHLTjM2MzgzP_MnGzMvQTM6cy_zP_M4QnMznvM7y1bNMzJzNV-zK7MhczmzOEwzN_MrczpzLUcX8zfzKViDzRhzOjMlcycNQxxzK3MuMzFQMzpJ0NFZ1VZb8zhzLbMnx9eITLMnkvMn8yMKWnM8Mysaj7M0sylVsyXzKglM3TMhczzKMyqditdMMznfzx3JcyPQWNNzJzMpVPM3gHM5AYTzPUFHMzIzOLMrczEzKbMzklyzLdFATFczOrM7My9Sszdb0vM5HDMkFfMthApKMzfzNTMmXU7T8zEVMyfzM3MgszJzP7MlMy3zNwwzMwrzKPMuW7M2h1laTZ8DUEJzNknzMjMhWrMiaFj3AAgbszDzP0zBBoOzP7Mw8yNzPxmR8zPzJjMmnXMsxbM5cyxbMziDsyfOGPMkn83Hcy5", + } + ], + "issuanceDate": "2024-07-11T14:16:37.818699601Z", +} + + +MOCK_W3C_PRES_REQ = { + "version": "1.0", + "name": "Age Verification", + "nonce": "10275921379803826423", + "requested_attributes": { + "age-verification_attribute": { + "names": ["name", "degree"], + "restrictions": [ + { + "cred_def_id": "XwfxmPHisu4ZeZJBxkU9yG:3:CL:1255397:faber.agent.degree_schema" + } + ], + "non_revoked": None, + } + }, + "requested_predicates": { + "age-verification_predicate_0": { + "name": "birthdate_dateint", + "p_type": "<=", + "p_value": 20060711, + "restrictions": [ + { + "cred_def_id": "XwfxmPHisu4ZeZJBxkU9yG:3:CL:1255397:faber.agent.degree_schema" + } + ], + "non_revoked": None, + } + }, +} + +CREDS_W3C_METADATA = [ + { + "schema_id": "DyZ6N8yDTQsdJazNGRG1Wa:2:degree schema:94.46.71", + "cred_def_id": W3C_CRED_DEF_ID, + "proof_attrs": ["age-verification_attribute"], + "proof_preds": ["age-verification_predicate_0"], + } +] +CRED_DEFS = { + W3C_CRED_DEF_ID: { + "issuerId": "DyZ6N8yDTQsdJazNGRG1Wa", + "schemaId": "1255405", + "type": "CL", + "tag": "faber.agent.degree_schema", + "value": { + "primary": { + "n": "78608620890873858734716139623637557207236748041036599152087860745126232800410792474686186054868968424497768674471659783477234573895718704536655822053872184575008614841292292755773977336348717399858827225516378890147249022988821063695512091515834364155476868945304641152797312953150054985407637208945958685410474825963780210138068617216701521772433881960713239497125537276072745050872848837052182246304003140975539152490891455762930011410881319057199204939478636329525536320694776615565703536734007580747895464767619347642637144494483687798355974644728869518384135823542735258079646551610214657144799764002337455867901", + "s": "16188463849412546891640922552292465562838101367601277992128848162820202387935769608332958870545695803678135322910482469331967995649945684153453613428194936775252383829449263033940304402931776444119090156613642012390697464244089486711501043789784798197678982862606452646894440861748599500239708128926125180765218509712903580782249517852590710823884952607759544656047904494741963723819532571620288071623944725608849021321079727999632523648744821503554842477891408906567788717005344644282892621994511877629914049255769558464858232935357888813001725324739131860348653048620151868133228245279216651937803301007796859809148", + "r": { + "birthdate_dateint": "42321523370621472586426295102920092200716331198439684880751631005169912502513392348097827405713382392870351057417515373100336421627456337831914687021550420466679438139143550828676924942214829658708598705747050568068185496205586201490901792162868231737144423909851894492126047402918174761809991274506216232444807425545257717043121113480192917245436523222737120696808266827384291992173584922757202272224069178866180763756530481426792811525864900148511627086927934936010624074649095185853314015773559488856913353502772303104846002365950937203557327886208101237935658269328817806137015577417487042129268739188266656455838", + "date": "47282259420559086943282354673047808620932419444685357054526207482261794311207196772606516809551819368209083141763687874977322077707872712376289631235921781401772584236188435782335773816464936538343314437193149188695496363754402835761105905586890326049759144994498739076391071587660559576777847661304403271245065165765908143795884639501186137644910876556367493709012019351539913881016951128164156123676486321940301768797942946239395712213761026864750547641644598432981007440760115711494359109796696075613999758983159720327332976592446600931387265379696287489201164302606210759812951251459749023665486884533213382958924", + "degree": "60153759390848714691146484479392274133562816428435528963700343565611839246367965177204366729270994987186528861839431634550705896062648963500744334263396360649172661053623663271877417714825487333228521750963805284497902848380144500097326818107221339461337355057464350057644489048251733402813904879274691818024566869295619847347992487894269500406434050269901008003427810448810680954782112900343085845895480492152666064981934626021734769089741480544408251390015024949353947950985833441693746870940371240088472660349008250865323036018382101819925605477908670083234510669977333706521636643517669757263712285651480671951438", + "master_secret": "69257669973177891195832782226385437484565319036176021981079514766058012037020075082472532547922159493694471894000485304834996412469208708596022878079539321977476360893056967116987888112646019355423293211625152730912914998240197562948008429593765189331528456680630558434041646526770282490181462733482684331422552007518675879888699350145492730604666054815294351263210025713657554257619799792234183125395509745994947644401679118272165189772096427816057022796046378025001876575565286124494995797524148310731022099328971450166406932241022324582104980165553352886278647451559452244959090635336848709227787779160570063933995", + "name": "20458995966529508877165523940497074335676254937851332719993093001097100744552306136953000062015369385339571060757160555146949716351802164339509695035318750654149402579329767870427364614476802086365989932781978496588454470338424829968021806632220468888978073679053857540337854521557335332056805950661368230443201713983136437108228688581590915142044148670631100462101211547552233924677659787731955302143306592219729468409193258738692878243862745911138912819040092930225998295659341590790374387917484110201330953217976441156017380252924144810828368472774513296773468638305997951977112244485597812185563323173743994253211", + "timestamp": "55767528953315429878051834231669793196600608692476348478822466838866892203667049770363514845399988470984876484824791194529693386828453840143191575371505007314911858032119317394684557071814305693207914561247007744526376234461663749763344626892275790375329303602003127448116512649256486837242640739786646268214738380126354252590557742599164523249833790304064018841314251464684190236688674401539272344244633494008666223871816336334479751355462072021761471760400457594130730361236036333007268192137073491655784241381271398943622768412747645159079022640144426459562916039147003053408876716780772931082906004155301164003958", + }, + "rctxt": "69242002209077646444514310939470989200750388630582386990437341114792494266313027866570067579621344642614422329584658119884298738951843345841692033119293233421722515070214666128612922992675853749542760511183059733091196542169569319157890885402821119674016451086771944772499261807433463241712959303954392628818958622889962938071354860935850496242072882611559360046105600408652229857914279710273402320462281517179305020310166125688712729993830879005117145310831038034017916827127155942964544984737924267675600573634963611253743739793194319943261044536689179850449758205865990098516834996191473012897372733669638888940862", + "z": "51242731616392012121352727847246541820577049021543380134688343217924953181612446379624475830535519309732251115093334802429445309772964258385091473539691104656210384235471360862671875319609225020080430096553962226044417812537000598208481560358691321186674211172554176620640649721800215424554979904593840857301101168898237019175668547291667328531278280287272373147669305081767897596818974802633242089621024618909466800155483416470100552474374035099404782672849917829899639003946061969254481700312516981709486034820809575367370664657305843859286826375363922294149444916134844356018258300050455851007457182607232004610457", + } + }, + } +} +SCHEMAS = { + "DyZ6N8yDTQsdJazNGRG1Wa:2:degree schema:94.46.71": { + "issuerId": "DyZ6N8yDTQsdJazNGRG1Wa", + "attrNames": [ + "date", + "timestamp", + "birthdate_dateint", + "degree", + "name", + ], + "name": "degree schema", + "version": "94.46.71", + } +} MOCK_PRES = { "proof": { "proofs": [ diff --git a/aries_cloudagent/anoncreds/tests/test_holder.py b/aries_cloudagent/anoncreds/tests/test_holder.py index 70a374d998..6c44e7c21b 100644 --- a/aries_cloudagent/anoncreds/tests/test_holder.py +++ b/aries_cloudagent/anoncreds/tests/test_holder.py @@ -1,6 +1,9 @@ import json from copy import deepcopy from unittest import IsolatedAsyncioTestCase +from unittest.mock import MagicMock +from pyld.jsonld import JsonLdProcessor +from pyld import jsonld import anoncreds import pytest @@ -15,14 +18,22 @@ PresentCredentials, RevocationRegistryDefinition, Schema, + W3cCredential, + W3cPresentation, ) from aries_askar import AskarError, AskarErrorCode from aries_cloudagent.anoncreds.tests.mock_objects import ( + CRED_DEFS, + CREDS_W3C_METADATA, MOCK_CRED, MOCK_CRED_DEF, MOCK_PRES, MOCK_PRES_REQ, + MOCK_W3C_CRED, + MOCK_W3C_PRES_REQ, + MOCK_W3CPRES, + SCHEMAS, ) from aries_cloudagent.askar.profile import AskarProfile from aries_cloudagent.askar.profile_anon import AskarAnoncredsProfile @@ -31,6 +42,8 @@ InMemoryProfileSession, ) from aries_cloudagent.tests import mock +from aries_cloudagent.vc.ld_proofs.document_loader import DocumentLoader +from aries_cloudagent.vc.tests.document_loader import custom_document_loader from aries_cloudagent.wallet.error import WalletNotFoundError from .. import holder as test_module @@ -63,6 +76,25 @@ def to_dict(self): return MOCK_CRED +class MockCredReceivedW3C: + def __init__(self, bad_schema=False, bad_cred_def=False): + self.schema_id = "Sc886XPwD1gDcHwmmLDeR2:2:degree schema:45.101.94" + self.cred_def_id = ( + "Sc886XPwD1gDcHwmmLDeR2:3:CL:229975:faber.agent.degree_schema" + ) + + if bad_schema: + self.schema_id = "bad-schema-id" + if bad_cred_def: + self.cred_def_id = "bad-cred-def-id" + + def to_json_buffer(self): + return b"credential" + + def to_dict(self): + return MOCK_W3C_CRED + + class MockCredential: def __init__(self, bad_schema=False, bad_cred_def=False): self.bad_schema = bad_schema @@ -77,6 +109,20 @@ def process(self, *args, **kwargs): return MockCredReceived(self.bad_schema, self.bad_cred_def) +class MockW3Credential: + def __init__(self, bad_schema=False, bad_cred_def=False): + self.bad_schema = bad_schema + self.bad_cred_def = bad_cred_def + + cred = mock.AsyncMock(auto_spec=W3cCredential) + + def to_dict(self): + return MOCK_W3C_CRED + + def process(self, *args, **kwargs): + return MockCredReceivedW3C(self.bad_schema, self.bad_cred_def) + + class MockMasterSecret: value = b"master-secret" @@ -303,6 +349,54 @@ async def test_store_credential(self, mock_load, mock_master_secret): {"cred-req-meta": "cred-req-meta"}, ) + @mock.patch.object( + AnonCredsHolder, "get_master_secret", return_value="master-secret" + ) + @mock.patch.object( + W3cCredential, + "load", + side_effect=[ + MockW3Credential(), + ], + ) + @mock.patch.object( + Credential, + "from_w3c", + side_effect=[ + MockCredential(), + ], + ) + async def test_store_credential_w3c( + self, mock_load, mock_w3cload, mock_master_secret + ): + + self.profile.context.injector.bind_instance( + DocumentLoader, custom_document_loader + ) + self.profile.transaction = mock.Mock( + return_value=mock.MagicMock( + insert=mock.CoroutineMock(return_value=None), + commit=mock.CoroutineMock(return_value=None), + ) + ) + + with mock.patch.object(jsonld, "expand", return_value=MagicMock()): + with mock.patch.object( + JsonLdProcessor, "get_values", return_value=["type1"] + ): + result = await self.holder.store_credential_w3c( + MOCK_CRED_DEF, + MOCK_W3C_CRED, + {"cred-req-meta": "cred-req-meta"}, + credential_attr_mime_types={"first_name": "application/json"}, + ) + + assert result is not None + assert mock_master_secret.called + assert mock_load.called + assert mock_w3cload.called + assert self.profile.transaction.called + @mock.patch.object( AnonCredsHolder, "get_master_secret", return_value="master-secret" ) @@ -619,6 +713,54 @@ async def test_create_presentation_create_error( rev_states={}, ) + @mock.patch.object(InMemoryProfileSession, "handle") + @mock.patch.object( + AnonCredsHolder, "get_master_secret", return_value="master-secret" + ) + @mock.patch.object( + anoncreds.W3cPresentation, + "create", + return_value=W3cPresentation.load(MOCK_W3CPRES), + ) + async def test_create_presentation_w3c( + self, mock_pres_create, mock_master_secret, mock_handle + ): + mock_handle.fetch = mock.CoroutineMock(return_value=MockCredEntry()) + await self.holder.create_presentation_w3c( + presentation_request=MOCK_W3C_PRES_REQ, + requested_credentials_w3c=[W3cCredential.load(MOCK_W3C_CRED)], + schemas=SCHEMAS, + credential_definitions=CRED_DEFS, + credentials_w3c_metadata=CREDS_W3C_METADATA, + ) + + assert mock_pres_create.called + assert mock_master_secret.called + mock_handle.fetch.assert_called + + @mock.patch.object(InMemoryProfileSession, "handle") + @mock.patch.object( + AnonCredsHolder, "get_master_secret", return_value="master-secret" + ) + @mock.patch.object( + anoncreds.W3cPresentation, + "create", + side_effect=AnoncredsError(AnoncredsErrorCode.UNEXPECTED, "test"), + ) + async def test_create_presentation_w3c_create_error( + self, mock_pres_create, mock_master_secret, mock_handle + ): + mock_handle.fetch = mock.CoroutineMock(return_value=MockCredEntry()) + # anoncreds error when creating presentation + with self.assertRaises(AnonCredsHolderError): + await self.holder.create_presentation_w3c( + presentation_request=MOCK_W3C_PRES_REQ, + requested_credentials_w3c=[W3cCredential.load(MOCK_W3C_CRED)], + schemas=SCHEMAS, + credential_definitions=CRED_DEFS, + credentials_w3c_metadata=CREDS_W3C_METADATA, + ) + @mock.patch.object( CredentialRevocationState, "create", diff --git a/aries_cloudagent/anoncreds/verifier.py b/aries_cloudagent/anoncreds/verifier.py index fe79aa13e7..9018a023a6 100644 --- a/aries_cloudagent/anoncreds/verifier.py +++ b/aries_cloudagent/anoncreds/verifier.py @@ -6,13 +6,14 @@ from time import time from typing import List, Mapping, Tuple -from anoncreds import AnoncredsError, Presentation +from anoncreds import AnoncredsError, Presentation, W3cPresentation from ..core.profile import Profile from ..indy.models.xform import indy_proof_req2non_revoc_intervals from ..messaging.util import canon, encode from .models.anoncreds_cred_def import GetCredDefResult from .registry import AnonCredsRegistry +from ..vc.vc_ld.validation_result import PresentationVerificationResult LOGGER = logging.getLogger(__name__) @@ -497,3 +498,70 @@ async def verify_presentation( verified = False return (verified, msgs) + + async def verify_presentation_w3c( + self, pres_req, pres, cred_metadata + ) -> PresentationVerificationResult: + """Verify a W3C presentation. + + Args: + pres_req: The presentation request data. + pres: The presentation data. + cred_metadata: The credential metadata. + + Returns: + PresentationVerificationResult: An object containing the verification result, + errors, and other details. + + Raises: + AnoncredsError: If there is an error during the verification process. + + """ + credentials = pres["verifiableCredential"] + cred_def_ids = [] + for credential in credentials: + cred_def_id = credential["proof"]["verificationMethod"] + cred_def_ids.append(cred_def_id) + + cred_defs = {} + schemas = {} + msgs = [] + + anoncreds_verifier = AnonCredsVerifier(self.profile) + ( + schemas, + cred_defs, + rev_reg_defs, + rev_reg_entries, + ) = await anoncreds_verifier.process_pres_identifiers(cred_metadata) + + try: + del pres["presentation_submission"] + + presentation = W3cPresentation.load(pres) + + verified = await asyncio.get_event_loop().run_in_executor( + None, + presentation.verify, + pres_req, + schemas, + cred_defs, + rev_reg_defs, + [ + rev_list + for timestamp_to_list in rev_reg_entries.values() + for rev_list in timestamp_to_list.values() + ], + ) + except AnoncredsError 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 + + result = PresentationVerificationResult(verified=verified, errors=msgs) + + return result diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/formats/vc_di/handler.py b/aries_cloudagent/protocols/issue_credential/v2_0/formats/vc_di/handler.py index 100b44530e..b418bfdf9e 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/formats/vc_di/handler.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/formats/vc_di/handler.py @@ -7,8 +7,8 @@ import json import logging from typing import Mapping, Tuple -from anoncreds import W3cCredential from ...models.cred_ex_record import V20CredExRecord +from anoncreds import W3cCredential from ...models.detail.indy import ( V20CredExRecordIndy, ) @@ -295,9 +295,10 @@ async def _create(): credential=credential, ) - return self.get_format_data( + cred_offer = self.get_format_data( CRED_20_OFFER, json.loads(vcdi_cred_abstract.to_json()) ) + return cred_offer async def receive_offer( self, cred_ex_record: V20CredExRecord, cred_offer_message: V20CredOffer @@ -461,7 +462,7 @@ async def issue_credential( async with ledger: schema_id = await ledger.credential_definition_id2schema_id(cred_def_id) cred_def = await ledger.get_credential_definition(cred_def_id) - revocable = cred_def["value"].get("revocation") + revocable = True if cred_def["value"].get("revocation") else False legacy_offer = await self._prepare_legacy_offer(cred_offer, schema_id) legacy_request = await self._prepare_legacy_request(cred_request, cred_def_id) @@ -559,21 +560,22 @@ async def store_credential( """Store vcdi credential.""" cred = cred_ex_record.cred_issue.attachment(VCDICredFormatHandler.format) cred = cred["credential"] - + try: + w3cred = W3cCredential.load(cred) + except AnonCredsHolderError as e: + LOGGER.error(f"Error receiving credential: {e.error_code} - {e.message}") rev_reg_def = None anoncreds_registry = self.profile.inject(AnonCredsRegistry) cred_def_result = await anoncreds_registry.get_credential_definition( self.profile, cred["proof"][0]["verificationMethod"] ) - # TODO: remove loading of W3cCredential and use the credential directly - try: - cred_w3c = W3cCredential.load(cred) - rev_reg_id = cred_w3c.rev_reg_id - rev_reg_index = cred_w3c.rev_reg_index - except AnonCredsHolderError as e: - LOGGER.error(f"Error receiving credential: {e.error_code} - {e.message}") - raise e - if rev_reg_id: + rev_reg_id = None + rev_reg_index = None + if ( + w3cred.rev_reg_id != "None" + ): # String None because rev_reg_id property wrapped str() + rev_reg_id = w3cred.rev_reg_id + rev_reg_def_result = ( await anoncreds_registry.get_revocation_registry_definition( self.profile, rev_reg_id diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/formats/vc_di/tests/test_handler.py b/aries_cloudagent/protocols/issue_credential/v2_0/formats/vc_di/tests/test_handler.py index 02a99804b2..57f130007b 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/formats/vc_di/tests/test_handler.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/formats/vc_di/tests/test_handler.py @@ -1,7 +1,7 @@ from copy import deepcopy from time import time import json - +from anoncreds import W3cCredential from aries_cloudagent.anoncreds.models.anoncreds_cred_def import ( CredDef, GetCredDefResult, @@ -869,22 +869,27 @@ async def test_store_credential(self): record.save = mock.CoroutineMock() self.handler.get_detail_record = mock.AsyncMock(return_value=record) with mock.patch.object( - AnonCredsHolder, - "store_credential_w3c", - mock.AsyncMock(), - ) as mock_store_credential: - # Error case: no cred ex record found + W3cCredential, + "rev_reg_id", + return_value="rr-id", + ): + with mock.patch.object( + AnonCredsHolder, + "store_credential_w3c", + mock.AsyncMock(), + ) as mock_store_credential: + # Error case: no cred ex record found - await self.handler.store_credential(cred_ex_record, cred_id) + await self.handler.store_credential(cred_ex_record, cred_id) - mock_store_credential.assert_called_once_with( - mock_credential_definition_result.credential_definition.serialize(), - VCDI_CRED["credential"], - record.cred_request_metadata, - None, - credential_id=cred_id, - rev_reg_def=revocation_registry.serialize(), - ) + mock_store_credential.assert_called_once_with( + mock_credential_definition_result.credential_definition.serialize(), + VCDI_CRED["credential"], + record.cred_request_metadata, + None, + credential_id=cred_id, + rev_reg_def=revocation_registry.serialize(), + ) with mock.patch.object( AnonCredsHolder, diff --git a/aries_cloudagent/protocols/present_proof/dif/pres_exch.py b/aries_cloudagent/protocols/present_proof/dif/pres_exch.py index d383de8da5..72d7eef25e 100644 --- a/aries_cloudagent/protocols/present_proof/dif/pres_exch.py +++ b/aries_cloudagent/protocols/present_proof/dif/pres_exch.py @@ -37,6 +37,7 @@ def __init__( ldp: Mapping = None, ldp_vc: Mapping = None, ldp_vp: Mapping = None, + di_vc: Mapping = None, ): """Initialize format.""" self.jwt = jwt @@ -45,6 +46,7 @@ def __init__( self.ldp = ldp self.ldp_vc = ldp_vc self.ldp_vp = ldp_vp + self.di_vc = di_vc class ClaimFormatSchema(BaseModelSchema): @@ -62,6 +64,7 @@ class Meta: ldp = fields.Dict(required=False) ldp_vc = fields.Dict(required=False) ldp_vp = fields.Dict(required=False) + di_vc = fields.Dict(required=False) class SubmissionRequirements(BaseModel): diff --git a/aries_cloudagent/protocols/present_proof/dif/pres_exch_handler.py b/aries_cloudagent/protocols/present_proof/dif/pres_exch_handler.py index 0951f715f9..4cf39a4379 100644 --- a/aries_cloudagent/protocols/present_proof/dif/pres_exch_handler.py +++ b/aries_cloudagent/protocols/present_proof/dif/pres_exch_handler.py @@ -38,6 +38,7 @@ SECURITY_CONTEXT_BBS_URL, ) from ....vc.vc_ld.prove import create_presentation, derive_credential, sign_presentation +from ....vc.vc_di.prove import create_signed_anoncreds_presentation from ....wallet.base import BaseWallet, DIDInfo from ....wallet.default_verification_key_strategy import BaseVerificationKeyStrategy from ....wallet.error import WalletError, WalletNotFoundError @@ -194,7 +195,10 @@ async def get_sign_key_credential_subject_id( else: reqd_key_type = ED25519 for cred in applicable_creds: - if cred.subject_ids and len(cred.subject_ids) > 0: + if cred.cred_value.get("proof", {}).get("type") == "DataIntegrityProof": + filtered_creds_list.append(cred.cred_value) + issuer_id = cred.issuer_id + elif cred.subject_ids and len(cred.subject_ids) > 0: if not issuer_id: for cred_subject_id in cred.subject_ids: if not cred_subject_id.startswith("urn:"): @@ -399,14 +403,18 @@ async def filter_constraints( new_credential_dict = self.reveal_doc( credential_dict=credential_dict, constraints=constraints ) - derive_suite = await self._get_derive_suite() - signed_new_credential_dict = await derive_credential( - credential=credential_dict, - reveal_document=new_credential_dict, - suite=derive_suite, - document_loader=document_loader, - ) - credential = self.create_vcrecord(signed_new_credential_dict) + if credential_dict["proof"]["type"] == "DataIntegrityProof": + # TODO - don't sign + credential = self.create_vcrecord(credential_dict) + else: + derive_suite = await self._get_derive_suite() + signed_new_credential_dict = await derive_credential( + credential=credential_dict, + reveal_document=new_credential_dict, + suite=derive_suite, + document_loader=document_loader, + ) + credential = self.create_vcrecord(signed_new_credential_dict) result.append(credential) return result @@ -1073,6 +1081,7 @@ async def apply_requirements( credentials=credentials, schemas=descriptor.schemas, ) + # Filter credentials based upon path expressions specified in constraints filtered = await self.filter_constraints( constraints=descriptor.constraint, @@ -1199,6 +1208,7 @@ async def create_vp( challenge: str = None, domain: str = None, records_filter: dict = None, + is_holder_override: bool = None, ) -> Union[Sequence[dict], dict]: """Create VerifiablePresentation. @@ -1207,7 +1217,8 @@ async def create_vp( pd (PresentationDefinition): PresentationDefinition. challenge (str, optional): Challenge string. Defaults to None. domain (str, optional): Domain string. Defaults to None. - records_filter (dict, optional): Records filter dictionary. Defaults to None. + records_filter (dict, optional): Records filter dictionary. + is_holder_override (bool, optional): Override is_holder check. Returns: Union[Sequence[dict], dict]: VerifiablePresentation. @@ -1216,6 +1227,7 @@ async def create_vp( req = await self.make_requirement( srs=pd.submission_requirements, descriptors=pd.input_descriptors ) + result = [] if req.nested_req: for nested_req in req.nested_req: @@ -1248,11 +1260,10 @@ async def create_vp( "enabled. Please specify which credentials should be applied to " "which input_descriptors using record_ids filter." ) - # submission_property submission_property = PresentationSubmission( id=str(uuid4()), definition_id=pd.id, descriptor_maps=descriptor_maps ) - if self.is_holder: + if self.is_holder or is_holder_override: ( issuer_id, filtered_creds_list, @@ -1265,7 +1276,8 @@ async def create_vp( result_vp.append(vp) continue else: - vp = await create_presentation(credentials=filtered_creds_list) + applicable_creds_list = filtered_creds_list + vp = await create_presentation(credentials=applicable_creds_list) else: if not self.pres_signing_did: ( @@ -1282,36 +1294,50 @@ async def create_vp( result_vp.append(vp) continue else: - vp = await create_presentation(credentials=filtered_creds_list) + applicable_creds_list = filtered_creds_list + vp = await create_presentation( + credentials=applicable_creds_list + ) else: issuer_id = self.pres_signing_did vp = await create_presentation(credentials=applicable_creds_list) - vp = self.__add_dif_fields_to_vp(vp, submission_property) - issue_suite = await self._get_issue_suite( - issuer_id=issuer_id, - ) - signed_vp = await sign_presentation( - presentation=vp, - suite=issue_suite, - challenge=challenge, - document_loader=document_loader, - ) + vp["presentation_submission"] = submission_property.serialize() + if self.proof_type is BbsBlsSignature2020.signature_type: + vp["@context"].append(SECURITY_CONTEXT_BBS_URL) + if self.proof_type == ("anoncreds-2023"): + signed_vp = await create_signed_anoncreds_presentation( + profile=self.profile, + pres_definition=pd.serialize(), + presentation=vp, + credentials=applicable_creds_list, + challenge=challenge, + ) + else: + vp = self.__add_dif_fields_to_vp(vp, submission_property) + issue_suite = await self._get_issue_suite( + issuer_id=issuer_id, + ) + signed_vp = await sign_presentation( + presentation=vp, + suite=issue_suite, + challenge=challenge, + document_loader=document_loader, + ) + result_vp.append(signed_vp) if len(result_vp) == 1: return result_vp[0] return result_vp def __add_dif_fields_to_vp( - self, - vp: dict, - submission_property: PresentationSubmission + self, vp: dict, submission_property: PresentationSubmission ) -> dict: vp["@context"].append(PRESENTATION_SUBMISSION_JSONLD_CONTEXT) vp["type"].append(PRESENTATION_SUBMISSION_JSONLD_TYPE) vp["presentation_submission"] = submission_property.serialize() if self.proof_type is BbsBlsSignature2020.signature_type: vp["@context"].append(SECURITY_CONTEXT_BBS_URL) - + return vp def check_if_cred_id_derived(self, id: str) -> bool: diff --git a/aries_cloudagent/protocols/present_proof/v2_0/formats/dif/handler.py b/aries_cloudagent/protocols/present_proof/v2_0/formats/dif/handler.py index be0e456b25..316a2728c9 100644 --- a/aries_cloudagent/protocols/present_proof/v2_0/formats/dif/handler.py +++ b/aries_cloudagent/protocols/present_proof/v2_0/formats/dif/handler.py @@ -16,6 +16,7 @@ Ed25519Signature2018, Ed25519Signature2020, ) +from ......vc.vc_di.manager import VcDiManager from ......vc.vc_ld.manager import VcLdpManager from ......vc.vc_ld.models.options import LDProofVCOptions from ......vc.vc_ld.models.presentation import VerifiablePresentation @@ -146,6 +147,7 @@ async def create_pres( proof_request = pres_ex_record.pres_request.attachment( DIFPresFormatHandler.format ) + pres_definition = None limit_record_ids = None reveal_doc_frame = None @@ -169,7 +171,6 @@ async def create_pres( domain = proof_request["options"].get("domain") if not challenge: challenge = str(uuid4()) - input_descriptors = pres_definition.input_descriptors claim_fmt = pres_definition.fmt dif_handler_proof_type = None @@ -286,12 +287,26 @@ async def create_pres( BbsBlsSignature2020.signature_type ) break + elif claim_fmt.di_vc: + if "proof_type" in claim_fmt.di_vc: + proof_types = claim_fmt.di_vc.get("proof_type") + + proof_type = [ + "DataIntegrityProof" + ] # [LinkedDataProof.signature_type] + dif_handler_proof_type = "anoncreds-2023" + + # TODO check acceptable proof type(s) ("anoncreds-2023") + else: + # TODO di_vc allowed ... raise V20PresFormatHandlerError( - "Currently, only ldp_vp with " + "Currently, only: ldp_vp with " "BbsBlsSignature2020, Ed25519Signature2018 and " - "Ed25519Signature2020 signature types are supported" + "Ed25519Signature2020 signature types; and " + "di_vc with anoncreds-2023 signatures are supported" ) + if one_of_uri_groups: records = [] cred_group_record_ids = set() @@ -317,6 +332,7 @@ async def create_pres( # For now, setting to 1000 max_results = 1000 records = await search.fetch(max_results) + # Avoiding addition of duplicate records ( vcrecord_list, @@ -324,6 +340,7 @@ async def create_pres( ) = await self.process_vcrecords_return_list(records, record_ids) record_ids = vcrecord_ids_set credentials_list = credentials_list + vcrecord_list + except StorageNotFoundError as err: raise V20PresFormatHandlerError(err) except TypeError as err: @@ -346,6 +363,7 @@ async def create_pres( ) return + # TODO check for ldp_vp vs di_vc request and prepare presentation as appropriate dif_handler = DIFPresExchHandler( self._profile, pres_signing_did=issuer_id, @@ -359,6 +377,7 @@ async def create_pres( pd=pres_definition, credentials=credentials_list, records_filter=limit_record_ids, + is_holder_override=True, ) return self.get_format_data(PRES_20, pres) except DIFPresExchError as err: @@ -446,12 +465,6 @@ async def verify_pres(self, pres_ex_record: V20PresExRecord) -> V20PresExRecord: pres_request = pres_ex_record.pres_request.attachment( DIFPresFormatHandler.format ) - manager = VcLdpManager(self.profile) - - options = LDProofVCOptions.deserialize(pres_request["options"]) - if not options.challenge: - options.challenge = str(uuid4()) - pres_ver_result = None if isinstance(dif_proof, Sequence): if len(dif_proof) == 0: @@ -459,6 +472,7 @@ async def verify_pres(self, pres_ex_record: V20PresExRecord) -> V20PresExRecord: "Presentation exchange record has no presentations to verify" ) for proof in dif_proof: + manager, options = self._get_type_manager_options(proof, pres_request) pres_ver_result = await manager.verify_presentation( vp=VerifiablePresentation.deserialize(proof), options=options, @@ -466,6 +480,7 @@ async def verify_pres(self, pres_ex_record: V20PresExRecord) -> V20PresExRecord: if not pres_ver_result.verified: break else: + manager, options = self._get_type_manager_options(dif_proof, pres_request) pres_ver_result = await manager.verify_presentation( vp=VerifiablePresentation.deserialize(dif_proof), options=options, @@ -474,3 +489,15 @@ async def verify_pres(self, pres_ex_record: V20PresExRecord) -> V20PresExRecord: assert pres_ver_result is not None pres_ex_record.verified = json.dumps(pres_ver_result.verified) return pres_ex_record + + def _get_type_manager_options(self, dif_proof: dict, pres_request: dict): + """Get the type of manager and options based on the proof type.""" + if dif_proof["proof"]["type"] == "DataIntegrityProof": + manager = VcDiManager(self.profile) + options = pres_request + else: + manager = VcLdpManager(self.profile) + options = LDProofVCOptions.deserialize(pres_request["options"]) + if not options.challenge: + options.challenge = str(uuid4()) + return manager, options diff --git a/aries_cloudagent/protocols/present_proof/v2_0/formats/dif/tests/test_handler.py b/aries_cloudagent/protocols/present_proof/v2_0/formats/dif/tests/test_handler.py index dad7b374b2..61e7f5c250 100644 --- a/aries_cloudagent/protocols/present_proof/v2_0/formats/dif/tests/test_handler.py +++ b/aries_cloudagent/protocols/present_proof/v2_0/formats/dif/tests/test_handler.py @@ -1,6 +1,7 @@ from copy import deepcopy from unittest import IsolatedAsyncioTestCase from aries_cloudagent.tests import mock +from aries_cloudagent.vc.vc_di.manager import VcDiManager from marshmallow import ValidationError from pyld import jsonld @@ -2348,3 +2349,22 @@ async def test_create_pres_catch_diferror(self): ) as mock_create_vp: mock_create_vp.side_effect = DIFPresExchError("TEST") await self.handler.create_pres(record) + + def test_get_type_manager_options(self): + profile = InMemoryProfile.test_profile() + handler = DIFPresFormatHandler(profile) + dif_proof = {"proof": {"type": "DataIntegrityProof"}} + pres_request = { + "options": {"challenge": "3fa85f64-5717-4562-b3fc-2c963f66afa7"} + } + manager, options = handler._get_type_manager_options(dif_proof, pres_request) + assert isinstance(manager, VcDiManager) + assert options == pres_request + + dif_proof = {"proof": {"type": "LDPProof"}} + pres_request = { + "options": {"challenge": "3fa85f64-5717-4562-b3fc-2c963f66afa7"} + } + manager, options = handler._get_type_manager_options(dif_proof, pres_request) + assert isinstance(manager, VcLdpManager) + assert options.challenge == "3fa85f64-5717-4562-b3fc-2c963f66afa7" diff --git a/aries_cloudagent/protocols/present_proof/v2_0/routes.py b/aries_cloudagent/protocols/present_proof/v2_0/routes.py index b18b6041dc..a1e5b9f257 100644 --- a/aries_cloudagent/protocols/present_proof/v2_0/routes.py +++ b/aries_cloudagent/protocols/present_proof/v2_0/routes.py @@ -580,6 +580,7 @@ async def present_proof_credentials_list(request: web.BaseRequest): extra_query, ) ) + except (IndyHolderError, AnonCredsHolderError) as err: if pres_ex_record: async with profile.session() as session: @@ -726,6 +727,15 @@ async def present_proof_credentials_list(request: web.BaseRequest): BbsBlsSignature2020.signature_type ] break + elif claim_fmt.di_vc: + if "proof_type" in claim_fmt.di_vc: + proof_types = claim_fmt.di_vc.get("proof_type") + + proof_type = [ + "DataIntegrityProof" + ] # [LinkedDataProof.signature_type] + + # TODO check acceptable proof type(s) ("anoncreds-2023") else: raise web.HTTPBadRequest( reason=( diff --git a/aries_cloudagent/vc/vc_di/__init__.py b/aries_cloudagent/vc/vc_di/__init__.py new file mode 100644 index 0000000000..2f6f01422d --- /dev/null +++ b/aries_cloudagent/vc/vc_di/__init__.py @@ -0,0 +1,13 @@ +from .verify import verify_signed_anoncredspresentation +from .prove import ( + create_signed_anoncreds_presentation, + prepare_data_for_presentation, + _load_w3c_credentials, +) + +__all__ = [ + "verify_signed_anoncredspresentation", + "create_signed_anoncreds_presentation", + "prepare_data_for_presentation", + "_load_w3c_credentials", +] diff --git a/aries_cloudagent/vc/vc_di/manager.py b/aries_cloudagent/vc/vc_di/manager.py new file mode 100644 index 0000000000..96c7994987 --- /dev/null +++ b/aries_cloudagent/vc/vc_di/manager.py @@ -0,0 +1,31 @@ +"""Manager for managing DIF DI Proof presentations over JSON-LD formatted W3C VCs.""" + +from ...core.profile import Profile +from ..vc_ld.models.presentation import VerifiablePresentation +from ..vc_ld.validation_result import PresentationVerificationResult +from .verify import verify_signed_anoncredspresentation + + +class VcDiManagerError(Exception): + """Generic VcLdpManager Error.""" + + +class VcDiManager: + """Class for managing DIF DI Proof presentations over JSON-LD formatted W3C VCs.""" + + def __init__(self, profile: Profile): + """Initialize the VC DI Proof Manager.""" + self.profile = profile + + async def verify_presentation( + self, vp: VerifiablePresentation, options: dict + ) -> PresentationVerificationResult: + """Verify a VP with a Linked Data Proof.""" + if not options.get("options", {}).get("challenge"): + raise VcDiManagerError("Challenge is required for verifying a VP") + return await verify_signed_anoncredspresentation( + profile=self.profile, + presentation=vp.serialize(), + challenge=options["options"]["challenge"], + pres_req=options, + ) diff --git a/aries_cloudagent/vc/vc_di/prove.py b/aries_cloudagent/vc/vc_di/prove.py new file mode 100644 index 0000000000..3378eb4782 --- /dev/null +++ b/aries_cloudagent/vc/vc_di/prove.py @@ -0,0 +1,371 @@ +"""Verifiable Credential and Presentation proving methods.""" + +import asyncio +from hashlib import sha256 +import re +from typing import Any, Optional, Tuple + +from aries_cloudagent.anoncreds.registry import AnonCredsRegistry +from aries_cloudagent.revocation.models.revocation_registry import RevocationRegistry +from ..ld_proofs import ( + ProofPurpose, + LinkedDataProofException, +) +from ...anoncreds.holder import AnonCredsHolder, AnonCredsHolderError +from ...anoncreds.verifier import AnonCredsVerifier +from ...core.profile import Profile +from anoncreds import ( + W3cCredential, + RevocationStatusList, + CredentialRevocationState, + AnoncredsError, +) + + +async def create_signed_anoncreds_presentation( + *, + profile: Profile, + pres_definition: dict, + presentation: dict, + credentials: list, + purpose: ProofPurpose = None, + challenge: str, + domain: str = None, +) -> tuple[dict, dict, list]: + """Sign the presentation with the passed signature suite. + + Will set a default AuthenticationProofPurpose if no proof purpose is passed. + + Args: + profile (Profile): The profile to use + pres_definition (dict): The presentation definition + presentation (dict): The presentation to sign + credentials (list): The credentials to use for the presentation + document_loader (DocumentLoader): Document loader to use. + purpose (ProofPurpose, optional): Purpose to use. Required if challenge is None + challenge (str, optional): Challenge to use. Required if domain is None. + domain (str, optional): Domain to use. Only used if purpose is None. + holder (bool, optional): create a presentation or just the proof request + + Raises: + LinkedDataProofException: When both purpose and challenge are not provided + And when signing of the presentation fails + + Returns: + dict: A verifiable presentation object + + """ + + if not challenge: + raise LinkedDataProofException( + 'A "challenge" param is required when not providing a' + ' "purpose" (for AuthenticationProofPurpose).' + ) + + w3c_creds = await _load_w3c_credentials(credentials) + anoncreds_proofrequest, w3c_creds_metadata = await prepare_data_for_presentation( + presentation, w3c_creds, pres_definition, profile, challenge + ) + + anoncreds_verifier = AnonCredsVerifier(profile) + ( + schemas, + cred_defs, + rev_reg_defs, + rev_reg_entries, + ) = await anoncreds_verifier.process_pres_identifiers(w3c_creds_metadata) + + rev_states = await create_rev_states( + w3c_creds_metadata, rev_reg_defs, rev_reg_entries + ) + anoncreds_holder = AnonCredsHolder(profile) + anoncreds_proof = await anoncreds_holder.create_presentation_w3c( + presentation_request=anoncreds_proofrequest, + requested_credentials_w3c=w3c_creds, + credentials_w3c_metadata=w3c_creds_metadata, + schemas=schemas, + credential_definitions=cred_defs, + rev_states=rev_states, + ) + + # TODO any processing to put the returned proof into DIF format + anoncreds_proof["presentation_submission"] = presentation["presentation_submission"] + + return anoncreds_proof + + +async def _load_w3c_credentials(credentials: list) -> list: + """_load_w3c_credentials. + + Args: + credentials (list): The credentials to load + + Returns: + list: A list of W3C credentials + """ + w3c_creds = [] + for credential in credentials: + try: + w3c_cred = W3cCredential.load(credential) + w3c_creds.append(w3c_cred) + except Exception as err: + raise LinkedDataProofException( + "Error loading credential as W3C credential" + ) from err + + return w3c_creds + + +async def create_rev_states( + w3c_creds_metadata: list, + rev_reg_defs: dict, + rev_reg_entries: dict, +) -> Optional[dict]: + """create_rev_states. + + Args: + profile (Profile): The profile to use + w3c_creds_metadata (list): The metadata for the credentials + rev_reg_defs (dict): The revocation registry definitions + rev_reg_entries (dict): The revocation registry entries + + Returns: + dict: A dictionary of revocation states + """ + if not bool(rev_reg_defs and rev_reg_entries): + return None + + rev_states = {} + for w3c_cred_cred in w3c_creds_metadata: + rev_reg_def = rev_reg_defs.get(w3c_cred_cred["rev_reg_id"]) + rev_reg_def["id"] = w3c_cred_cred["rev_reg_id"] + rev_reg_def_from_registry = RevocationRegistry.from_definition( + rev_reg_def, True + ) + local_tails_path = ( + await rev_reg_def_from_registry.get_or_fetch_local_tails_path() + ) + revocation_status_list = RevocationStatusList.load( + rev_reg_entries.get(w3c_cred_cred["rev_reg_id"])[ + w3c_cred_cred.get("timestamp") + ] + ) + rev_reg_index = w3c_cred_cred["rev_reg_index"] + try: + rev_state = await asyncio.get_event_loop().run_in_executor( + None, + CredentialRevocationState.create, + rev_reg_def, + revocation_status_list, + rev_reg_index, + local_tails_path, + ) + rev_states[w3c_cred_cred["rev_reg_id"]] = rev_state + except AnoncredsError as err: + raise AnonCredsHolderError("Error creating revocation state") from err + + return rev_states + + +async def prepare_data_for_presentation( + presentation, + w3c_creds, + pres_definition, + profile, + challenge, +) -> tuple[dict[str, Any], list, list]: + """prepare_data_for_presentation. + + Args: + presentation (dict): The presentation to prepare + w3c_creds (list): The W3C credentials + pres_definition (dict): The presentation definition + profile (Profile): The profile to use + challenge (str): The challenge to use + + Returns: + tuple[dict[str, Any], list, list]: A tuple of the anoncreds proof + request, the W3C credentials metadata, and the W3C credentials + """ + + if not challenge: + raise LinkedDataProofException("A challenge is required") + + pres_submission = presentation["presentation_submission"] + descriptor_map = pres_submission["descriptor_map"] + w3c_creds_metadata = [{} for _ in range(len(w3c_creds))] + pres_name = ( + pres_definition.get("name") if pres_definition.get("name") else "Proof request" + ) + challenge_hash = sha256(challenge.encode("utf-8")).hexdigest() + nonce = str(int(challenge_hash, 16))[:20] + + anoncreds_proofrequest = { + "version": "1.0", + "name": pres_name, + "nonce": nonce, + "requested_attributes": {}, + "requested_predicates": {}, + } + + for descriptor_map_item in descriptor_map: + descriptor = next( + item + for item in pres_definition["input_descriptors"] + if item["id"] == descriptor_map_item["id"] + ) + + referent = descriptor_map_item["id"] + attribute_referent = f"{referent}_attribute" + predicate_referent_base = f"{referent}_predicate" + predicate_referent_index = 0 + + fields = descriptor["constraints"]["fields"] + statuses = descriptor["constraints"]["statuses"] + + # descriptor_map_item['path'] should be something + # like '$.verifiableCredential[n]', we need to extract 'n' + entry_idx = _extract_cred_idx(descriptor_map_item["path"]) + w3c_cred = w3c_creds[entry_idx] + schema_id = w3c_cred.schema_id + cred_def_id = w3c_cred.cred_def_id + + requires_revoc_status = "active" in statuses and statuses["active"][ + "directive" + ] in ("allowed", "required") + + non_revoked_interval = None + if requires_revoc_status and w3c_cred.rev_reg_id: + anoncreds_registry = profile.inject(AnonCredsRegistry) + + result = await anoncreds_registry.get_revocation_list( + profile, w3c_cred.rev_reg_id, None + ) + w3c_creds_metadata[entry_idx]["rev_reg_id"] = w3c_cred.rev_reg_id + w3c_creds_metadata[entry_idx][ + "timestamp" + ] = result.revocation_list.timestamp + + non_revoked_interval = { + "from": result.revocation_list.timestamp, + "to": result.revocation_list.timestamp, + } + w3c_creds_metadata[entry_idx]["rev_reg_index"] = w3c_cred.rev_reg_index + w3c_creds_metadata[entry_idx]["revoc_status"] = non_revoked_interval + + w3c_creds_metadata[entry_idx]["schema_id"] = schema_id + w3c_creds_metadata[entry_idx]["cred_def_id"] = cred_def_id + w3c_creds_metadata[entry_idx]["proof_attrs"] = [] + w3c_creds_metadata[entry_idx]["proof_preds"] = [] + + for field in fields: + path = field["path"][0] + + # check for credential attributes vs other + if path.startswith("$.credentialSubject."): + property_name = path.replace("$.credentialSubject.", "") + if "predicate" in field: + # get predicate info + pred_filter = field["filter"] + (p_type, p_value) = _get_predicate_type_and_value(pred_filter) + pred_request = { + "name": property_name, + "p_type": p_type, + "p_value": p_value, + "restrictions": [{"cred_def_id": cred_def_id}], + "non_revoked": ( + non_revoked_interval if requires_revoc_status else None + ), + } + predicate_referent = ( + f"{predicate_referent_base}_{predicate_referent_index}" + ) + predicate_referent_index = predicate_referent_index + 1 + anoncreds_proofrequest["requested_predicates"][ + predicate_referent + ] = pred_request + w3c_creds_metadata[entry_idx]["proof_preds"].append( + predicate_referent + ) + else: + # no predicate, just a revealed attribute + attr_request = { + "names": [property_name], + "restrictions": [{"cred_def_id": cred_def_id}], + "non_revoked": ( + non_revoked_interval if requires_revoc_status else None + ), + } + # check if we already have this referent ... + if ( + attribute_referent + in anoncreds_proofrequest["requested_attributes"] + ): + anoncreds_proofrequest["requested_attributes"][ + attribute_referent + ]["names"].append(property_name) + else: + anoncreds_proofrequest["requested_attributes"][ + attribute_referent + ] = attr_request + w3c_creds_metadata[entry_idx]["proof_attrs"].append( + attribute_referent + ) + elif path.endswith(".issuer"): + # capture issuer - {'path': ['$.issuer'], + # 'filter': {'type': 'string', 'const': '569XGicsXvYwi512asJkKB'}} + # TODO prob not a general case + # issuer_id = field["filter"]["const"] + pass + else: + print("... skipping:", path) + + return anoncreds_proofrequest, w3c_creds_metadata + + +def _extract_cred_idx(item_path: str) -> int: + """_extract_cred_idx. + + Args: + item_path (str): path to extract index from + + Raises: + Exception: No index found in path + + Returns: + int: extracted index + """ + match = re.search(r"\[(\d+)\]", item_path) + if match: + return int(match.group(1)) + else: + raise AnonCredsHolderError("No index found in path") + + +def _get_predicate_type_and_value(pred_filter: dict) -> Tuple[str, str]: + """_get_predicate_type_and_value. + + Args: + pred_filter (dict): predicate filter + + Raises: + Exception: TODO + + Returns: + Tuple[str, str]: predicate type and value + """ + + supported_properties = { + "exclusiveMinimum": ">", + "exclusiveMaximum": "<", + "minimum": ">=", + "maximum": "<=", + } + + # TODO handle multiple predicates? + for key in pred_filter.keys(): + if key in supported_properties: + return (supported_properties[key], pred_filter[key]) + + # TODO more informative description + raise AnonCredsHolderError("Unsupported predicate filter") diff --git a/aries_cloudagent/vc/vc_di/tests/__init__.py b/aries_cloudagent/vc/vc_di/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/aries_cloudagent/vc/vc_di/tests/test_manager.py b/aries_cloudagent/vc/vc_di/tests/test_manager.py new file mode 100644 index 0000000000..4a5cbf8adc --- /dev/null +++ b/aries_cloudagent/vc/vc_di/tests/test_manager.py @@ -0,0 +1,244 @@ +"""Test VCDIManager.""" + +import pytest + +from aries_cloudagent.anoncreds.registry import AnonCredsRegistry +from aries_cloudagent.tests import mock +from anoncreds import W3cPresentation +from ....core.in_memory.profile import InMemoryProfile +from ....core.profile import Profile +from ....resolver.default.key import KeyDIDResolver +from ....resolver.did_resolver import DIDResolver +from ....wallet.default_verification_key_strategy import ( + BaseVerificationKeyStrategy, + DefaultVerificationKeyStrategy, +) +from ....wallet.did_method import DIDMethods +from ...ld_proofs.document_loader import DocumentLoader +from ..manager import VcDiManager, VcDiManagerError +from ...vc_ld.models.presentation import VerifiablePresentation + + +CHALLENGE = "3fa85f64-5717-4562-b3fc-2c963f66afa7" +OPTIONS = { + "options": { + "challenge": CHALLENGE, + "domain": "4jt78h47fh47", + }, + "presentation_definition": { + "id": "5591656f-5b5d-40f8-ab5c-9041c8e3a6a0", + "name": "Age Verification", + "purpose": "We need to verify your age before entering a bar", + "input_descriptors": [ + { + "id": "age-verification", + "name": "A specific type of VC + Issuer", + "purpose": "We want a VC of this type generated by this issuer", + "schema": [ + {"uri": "https://www.w3.org/2018/credentials#VerifiableCredential"} + ], + "constraints": { + "statuses": {"active": {"directive": "disallowed"}}, + "limit_disclosure": "required", + "fields": [ + { + "path": ["$.issuer"], + "filter": { + "type": "string", + "const": "GUBgy9MWF5KdmBjsC2wgTB", + }, + }, + {"path": ["$.credentialSubject.name"]}, + {"path": ["$.credentialSubject.degree"]}, + { + "path": ["$.credentialSubject.birthdate_dateint"], + "predicate": "preferred", + "filter": {"type": "number", "maximum": 20060710}, + }, + ], + }, + } + ], + "format": { + "di_vc": { + "proof_type": ["DataIntegrityProof"], + "cryptosuite": ["anoncreds-2023", "eddsa-rdfc-2022"], + } + }, + }, +} +ISSUER_ID = "TNuyNH2pAW2G6z3BW8ZYLf" +VC = { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/security/data-integrity/v2", + {"@vocab": "https://www.w3.org/ns/credentials/issuer-dependent#"}, + ], + "type": ["VerifiableCredential"], + "issuer": ISSUER_ID, + "issuanceDate": "2024-07-09T14:44:36.380996753Z", + "credentialSubject": { + "name": "Alice Smith", + "timestamp": 1720536272, + "date": "2018-05-28", + "birthdate_dateint": 20000709, + "degree": "Maths", + }, + "proof": { + "type": "DataIntegrityProof", + "proofPurpose": "assertionMethod", + "verificationMethod": "TNuyNH2pAW2G6z3BW8ZYLf:3:CL:1230422:faber.agent.degree_schema", + "proofValue": "ukgGEqXNjaGVtYV9pZNkvVE51eU5IMnBBVzJHNnozQlc4WllMZjoyOmRlZ3JlZSBzY2hlbWE6ODQuMjEuOTirY3JlZF9kZWZfaWTZPVROdXlOSDJwQVcyRzZ6M0JXOFpZTGY6MzpDTDoxMjMwNDIyOmZhYmVyLmFnZW50LmRlZ3JlZV9zY2hlbWGpc2lnbmF0dXJlgqxwX2NyZWRlbnRpYWyEo21fMtwAICvM6gcffsyHT8ycH8y_zJQIBGvMpMzHDsytSkYRfMzLzM7MqcyScMzUzLk9AlGhYdwBAQENzMDM8MyJG8yhzIwAzPx9zPEDzKnM5czVzOfMxMyFBMyCzOsPZcygzJ8qzKdmzPHMkMznzMLMpkllzO5kY8yceUZ_Qsz1zPxmzJvMjEoeLyrMy8yUNSkhzNXMg0vMgczXzPbMwVbM88yzLALMjsyxzI_Mq8ziHVjMh8z4zLUxZjPM6El5D8ycB8zqzMTMy0I4BFHM9sz2zJHMlMy0c8yECFbM7szUzN_M_szGzNs0TGcrzI7M98zyzPfMqDjM5k7Mn0vMlcz9bVjM62ZwIMyvO8zKzIhVdXrMs2DM9BbMr8yNYMy7zNHMkhvMsA53zMXMlGQ1PczpzM3Ms8yffcyEeDYIzJpjzKHM6syNMULMjczPaSlVzIZaMMyTK8zvzNHMo8zUPh7M0syufW0wzJHMolnMiC9Sa8y7zLk_G8yIUMzBzPLMlcyQYnfM9cy4QMz5Ckg1YEl9zMXM-xxGZ0xZD8zdzJ_M6cy1WGbMkkNLMsyiN1_MoDpuzNjM7WsdIMyTPFqhZdwASxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHbMwwLMoszPzLDMjsz4zN_Ms20ga17Mu6F23AFVCVluBkc_OMzvMsyAzJANzOUJfQvMlV3MnkzMj0RfzLpZzOXM12ZmzI7M9syHES8UNwjMiMzVzOQLcMz-zJ7Mn19szODM88znzMbMmVnMkczpzKbMyD7MvyHMy8y9zKFqE3tUdcyENyUCL8y8G1HM2cyZzPkBzO_MggLMiMzKzIbMxszkdMyVZ3UIzJ3M58zcbwVMzPDMwszjSszUEC9Ma8z_f8yVBczrCwPMmDfMylDMniZCzIx9bnogEsz4zK18zO3M3FUAdA7M3cyAG8zmaRTMncySIgszzKjM6SXM4szoe3wazNHMvBc2RczLzKjM2Aowc8zLR8ywzLvM_T_Mp3HMmCfMrczKGsyyzOlvzIxvBhxtCMykzM4KNithzM7MiDHMpBrM2szIzJA-UhHMvk8nzKvMqxIhDsytzM_Mp20jbHp4Fld8ccyRzNQEDGjMhVdVzMAiMQzMuVtUzMfMvGjM-cy0UsyIZMyXzLrM0GDMnnVhD03M4SJcWszBX8zuzOvMsszhzM1mzPQszIHMnAA5RMyhzP7MtMy0zMZzbyFRzP7MiSwczLXMml5BdyfMh8z7zIjMgsyZzLUzfUvM0MyAaszJzJ0YzPNhK8yPd8zJf8yUzL8rESnM3EI2zLbMzUTM-WTM6xYqzLLM_n4SeHfM36xyX2NyZWRlbnRpYWzAu3NpZ25hdHVyZV9jb3JyZWN0bmVzc19wcm9vZoKic2XcAQDMmAJ-zITM6czSElnMxMzCEsywa17MlWVEzP0SzNs1bGrMjszHX8zwB8zSJyRJzO9gXijMpsyWUScgcwxxzPHM9ULM6szPfCMHXcznAWQqbz4RzONpzJQKGsywTMzIaczazOtbzPMHNAEBzM5VzN7M3cy6zLzMmczYQUPM98z5zL3MsMykLsyQTmlvzLDMvzrM3sybzPHM6EgBzMPM9kx1zME3L1HMn8yCzNLM6BnMiiXMukXMlzjM9czvO0kizObM68ykzKPM78yyzJU5ccy4zLdQXynMx8yrFsy7zMoIzLhGPBTM08yPSMzUDwIXzKrMmkDM_MyqTFR0X8yGS2fMjsztecyNHXB1zPvMmSTMuEfM6czGUsyRzJbM_sykzNMXzJ1LasyVXDERzOoKWzUYGX0QzMoAzPsqzJXMpczdzOTM8CYrzNtczKTM7wtgNSARzNrMqHXMnlhQQ1IjzM1AUMzkfsyuSRcOJH7M2G4nzKNrOg-hY9wAIHXMx8yQzNDMpsyOzINHzIRADhkQJAQuzKvMrszTGsy2GgQWzLvMrUfM6SYwbzs", + "cryptosuite": "anoncreds-2023", + }, +} + +VP = { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/security/data-integrity/v2", + {"@vocab": "https://www.w3.org/ns/credentials/issuer-dependent#"}, + ], + "type": ["VerifiablePresentation"], + "verifiableCredential": [ + { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/security/data-integrity/v2", + {"@vocab": "https://www.w3.org/ns/credentials/issuer-dependent#"}, + ], + "type": ["VerifiableCredential"], + "issuer": ISSUER_ID, + "credentialSubject": { + "birthdate_dateint": True, + "name": "Alice Smith", + "degree": "Maths", + }, + "proof": { + "cryptosuite": "anoncreds-2023", + "type": "DataIntegrityProof", + "proofPurpose": "assertionMethod", + "verificationMethod": "JaAFqv91MMWh7Ne7aNPXCf:3:CL:1242241:faber.agent.degree_schema", + "proofValue": "ukgKDqXNjaGVtYV9pZNkvSmFBRnF2OTFNTVdoN05lN2FOUFhDZjoyOmRlZ3JlZSBzY2hlbWE6NDIuODkuNTirY3JlZF9kZWZfaWTZPUphQUZxdjkxTU1XaDdOZTdhTlBYQ2Y6MzpDTDoxMjQyMjQxOmZhYmVyLmFnZW50LmRlZ3JlZV9zY2hlbWGpc3ViX3Byb29mgq1wcmltYXJ5X3Byb29mgqhlcV9wcm9vZoaucmV2ZWFsZWRfYXR0cnOCpmRlZ3JlZdwAIAEEzIFizO3MnzzMkDFNzK0UG8yCzN7MtEnM28yabszSGCskzMfMi8yoF1xcbSSkbmFtZdwAIMyKzOENzPzMmmnM-X3MzMyRzOHM9RvMzszTzK_M9ELMvFfMqGnM4EEmzL0MzKfMy0rM8sydp2FfcHJpbWXcAQDMusyxbzYxzMTMsszEOcyjzOBZzNA8Pl3MsUbMomXMqcz3zJA8zL7M2MzlzIDMnsz7zLPM4yEea3hqTU3M_syMzI45ZMy3zIjM5czmzIjMoMznzMZLD8zfEsyUzKHMmmg-CmAmzLJXTXpzzLBUJcymVSbMtgxdbEpZzI1ozJ1pzKYrAczgzMNBzIXM9mFSzIwCzONazLnMm21RFjs1zJjM4sz8BMz6F8yDzK5XzM3MsDcYZTtsQMyCzOsSzIXMx8ziExzM4MzvzIXM08zOcE_MpczUMXg8zPDMt8zNBcyyMsytKX0FzPPMlMzCDcyozPjMrnHMhEY_J2clzJE5zP9vzNjM9kHMlg_M9cyLzOh3c18BKczozNDMomPM-hLMmMzazM3M3VpAzK7Mpsz0YszkXic6NRxVzL9hzPRKbBPMx8zgzKx_zL89zMbMsjdfzJRwBw4OfxROzNTM7cyqBgkWzNNqb8y2zPfMozzMyszpOcyRzKQbOMyqTTTMj1ihZdwAOSnMgAPMymwYzJrMjsygCm7MkcztVwQnY8yBWXnMkE1xEXfM8syzIczVIMzOzMLMksyazN4cOmVjVxl3LRzM2VIdflnM-CrMuyZYzLIvdaF23AF_C3w7zJrMiMzHzJbM-Mz6YXxGzMvMo8z_f8zfTMyzFXItSMzkD8zdzP52zMpVaczFD3wpajjMkMyfUTHMisykzKrMu21DzIA2zO8czKQuzMpvzMNIG8zzzI_MpcyuFkzMrMzkaMz1zLYiX8y9NMylzLvM_FwUzN1fzMoDzL5UDQHM7hRdzKEVzNzMgX9ZchPMucyCfDMQzMLM0Mz8e8yYZjzM1czRdXbMg0ICLMzFzLrMy8zNzIVyWSnMr8zSzKp4zLfM53AGAcyZzJ_Mm8yYESLM-MyszOcZOMzSN1N7zN_M1szKUczPJczezKfMz2PMx8zOKkcezKxSzPXM18yiNxVMzOrMgMzJzK3M1szoTszczI0VRR5OSMzBzNXMucy6zL4aY8zAzKI8d8zezNfMkMyezMHM1D_Ml8zPzPhvzNbMixzMt8zvJszyzMtbaBnMth7MpMymBsy-zKVCW2bMpHPMqitAzOrMrczzzIAazNXMkczGzMgFWMyiP8y5ahZ8zOA9CgjM5MzVzK_MokTMiczLzJZrzP0ozKHMtMyYzK1-zKEwzLDM7MytLigRMsz9DszUzP5qzI9SGcy5zOI1R8y6Tm0wImhPLszlUMzPD0lnYkQyOzgsb8yMzMzMpczAZ8yeQcyozIrM3cy6BcyUAszzzIrMoyIVzL_Mv0p8zM7M-3nMrQnMpndwTsyvcczsH8zYzILM7EjM31kULszAzJ0nBjsbTMzgzNddzOrMxg7MjMyBzMLMjczczLfMg0xafi3MtMzuoW2EpGRhdGXcAErMv8zYzNUlYyBzWcyXZsyFzLBlzIrM2hcXzIJZzMDMusyWBGrMjcyiAgPMuCvM08yKZsykzMtfPMyCCwh_zNjM6sybFczgzPJBGMz8OszKUczezMzM88zZGcyQA1LMy8yqzNsBfMzCPyJyzKYvzL08sWJpcnRoZGF0ZV9kYXRlaW503ABKzPJ0zOc6LDTM6syUzLMAVsz_cczBzOpSDRxyzIl3L8zyzLFxCczBzPbMgRxYOsznzN_M61bMzczYzLFBLszpzIY2zNDM7szQeFTMjMyPzI4NbMyXZHPMz8y7A8ztMczRzNFmejfMsMzea8yRzLnM33-tbWFzdGVyX3NlY3JldNwASsyGUTvMh8zVGwPMysykKMy7awrMgcy_Zx3MsRs6zIJTzPbMkH_M0nxMPVBUzPjMtcz8zKDMu0jM6yJHWcylzO7M2iwrzJ5KRSEYeA_M6syZPE9AISoGaMyWQMyEWMyuH8z8WczLYszCzMypdGltZXN0YW1w3ABKzJDM_l4SzIjM8hbMgTzMuMySVik4OcyPYB5XJcybzMRBzI7M_syVzN_MxT7MlMywzMMsS2HMj0HM88zjzN7M4cyyzO0lzIM3VwIKzLDM2FN7YghVREklc1YvQMzHIszVUVrMxMz8zPohWGGibTLcATA4NVtazKLMy17MqszaW8z1zIoUzKPMyyHMyHrMlSouc1VnA8zpPndQKTpfDC_MhMzOYMzQA8zYzM7MiMyDzMTMsMyBCMy2zNZ8BRN9zMXMrRRrShHMuXVDzI1gYD5seRfMgsycXHfMjx_Mm8zUdBAnzPPMhW4mG0jM5RzMhsy5zJPM5EtkzLA3zKIvzPFdzN1iSczbeBDMhGRLzM_M9BJzzJHMy2kazMx8zKFdzJnM6gbM98yiUMyODnJfzKx9ZEBheCknMD5zzJx9zM1RzKfM7GHMgyrM-2UdzP3M73zM0wbMi8zXdBzM5UfMz054EMzWzPoxzLgbO8zOMEXMrirM8nLM6HUrd8zVzIzMpDAXIklKzNTMm0h9e8yyAMzOzI7MxzlxzJBLzM_M6zZYdi7M_H7Mq8yJzPHMlWHMq8zyzPjM6X4-YMyZajrMqsy2zKfM6UHM3MzFHMz7zOISzIEgzJ9zzILM0MzqzJXM51zM2l3My2YBzJgqzJcLanvMr8z4X8yVWyImEMzSzIDMqkdadsyWLX3MnCLM_8yfzI1LbszHzIBLzMluBsyPZxTMjAgmzOTM5MyRIcyNqWdlX3Byb29mc5GGoXWEoTDcAErM81xrFsyWYGnM-czSzJttEEVXzMfMpcz5zMlYzJDMvcyfMBDMh8zYzIrMlhDM6My7zPZHzOxzzItmAERFRSwofczCzPDMoczXN8yfO8zYKczYzJ0aOXTMhUbMiMyEzIcvAizMu8yqzNsQSMyNzMpvoTPcAErMzMy5zJw3X8yYzM05Rg3MmMzIzO7M5j_MhzgzzIjM2BPMoMy_zLXMpnRVzO8pYsz1zPDM8cz_M8zPY8ytLszpFcyiURfM8lxgzIDM5XYHPF3MucyOzN0qdzdJzNl8zI3M4HhdXszazKs0SxFTT6Ex3ABKzPjMzMyrR8yKH0Q4zOJLzPkLS8yQzIjMrcyMMWFEzMHMhHERzJdOzP0azIjMnMz9zOzMnlQUzIAPQwEcZcyTWgvM2lViUszzzM3MrRXM2szZzLI9OlhpCcz4zKUVIQEpLXF1QszufsyiOqEy3ABKzOvMpcybOMz_zJvMp8zNBw5JzNYFLHbM1H1QzPY1V8znzK7MkMzazNQHQ8z3JXPMsAxUzP_Mr8yrzNV3zKPM60NtzMVZzMjMtMyCV8zyzL_MjczyK8yvasyFzKTM31LMlTrMhFdvCl1rzIrMtVPMu8ztEaFyhaEy3AEqA8ztzIDM3MyST8yczI3Mj8yAzNNezNRRNnrM3czNzI_M78z5zLU1zPTMu8ymzP_M7syBQUzM8lB9zO3MkszYZszUS8zmMl4YMnzMgwzMtMzselPMyMyBzKRxXXrM5F3M3czhzN0VJAkhzLrMi1jM-syuHQPMrsyKzLrM8k7M1Mzjbkw4zIFXzI3MlszfzIfMki3MlszJzN_MuszgzIHMpsz9QMzoasyIcX3M_DR1zKPMwV3MgkLMiQtGzLPM88yaM8yozI9KT1A9Oi3M43nMz8yazIHMy3jMqszpzLnM0czCzPfM0wY9C8zcW8ywzOnMjMzVbcy8O8z8HwfMuMyQfAkxVyp6zJPMosz1ccz_HlU8RszbzJfMg8zzzPFUzII0OszcdMySzNnM2synzM8_R1bMmQpZzLkHzP7Mr8yBZMyLHszyzNPM28yFJi1FzPhAUsymQ8yAFMz_FkDMo8yRZUpISUpjAMzwzIXMoQ_M6DPMisztzPTMrFLMlsyVzPc3RlHMxcyNGHhpAczRzP7MnszEBVTMxCvMpyfMwMz0aMz_zPHMoQsHzLRqzMZGQ2rMr1jMpwZSZ8ySWsyczK4SHGTM1syfzPoDzIpszO2lREVMVEHcASoHzMnMnczZzPjMkcziPXF_Q8ziC8zezI1jKypQKkEkUczizOIYzLIKzLHMpn41aMzRzOnMhszYzM0izMPMp3vMnCpIcFEBHyfMxszCFnDM_2ISzLlyTkDMizkczPPMpCvMolM_zOVyzOXM-UZJzMzMq8zvKAsKV8yQzI7MtMyYFMy0zLPMwwtGzJlNbw8WzMrMhwIDzLVNUBDM4mMXR8yiVMzZzKdezL99VCpgdUfMksydzNbM-1lyK8z0RMy3zL3MzCLMrMzGzO7M_MzLMkTM98zUzMs3zIlWGRMtzK7M9cz0zPTMiMyoAXd-AMzIzNTMs3s4zIHMrFIQclYMzN_M68ywzPzM2czlJ2nM2sy7KMybEMzfzITMzAPMlczoFMy2LszgZ8zQzP3M9wLMtH8yzPYzRzdubVB9zN_Mysy3GsyicEfMwQg0C8yAzL1hzKLMo8zfzMjMyk3M90nM103Mw8z-XMyozPsjzPkxzP7Mg1_MwszgzI13zMfMg8zezLDMh8zhzMhOzPQ_S8y0zNZTzPzMvDXMgcyZJFNdJsy_zLbMj8zgYczXzPtizIAZbGbM48ydBADMphbMzszvZ3bM2cy7oTHcASoIzNHM2MyqzP7MkShNzLLMsMz6zLDM3sylzInMk3NtzLVEzKTMiznMpFsKZXkQKEcZzOd8zO58zKkAF0XMi0tKzKUfzJFCzP7M58z4UCzM4AFnzPrM6kjMuSfM1yTMyxQDJCvMo8y-zKAZzKjMqSVwzNPMwFpdcczpzLNwUiZezOtNHBHM7Mz_LMyFecytzO7MwTtUA8y-KMz7zMrMkcyizM_M2F3MrsyPT8zLE1JozOR2zOjMwMyzAWXMyWjM9MzizLkFEszDzJJASCvMzU_Mz8zczIwjzKVHzINtzIHMi8zAecyyHszRAMzsJcyUPFhTzIbMvsyizNZwzJrM_8zTzLzM7cycf8zvzLp6KjxFzJ7M9cyNFn4wDcy4OMzmzIBNCszVzKnM_mLM3HvM637M3syczNLMp8zizOjMswXMlsyOzNPMyVF4IRDMhmrM5MzJzPzMisy5VmVFzOVAeMz3zI7M-8zTzPLM_8zlzMLMkHfM_XdpzInM6B0mzOknVUoeGwTMyicpzJJ7YjU2KMyKFcyAzIYrVsyTJszuzOfM_SLM5RzMp8zMQmp8zNDM3MylI8yvM8yeYWvMisz5zJtec8y3zMLM38z0oTDcASnMqjEHEzlfzMNqFl7M88z3zIUqzJNrzLt5zJ08dzzMkTnMnsyQzPkLzPnM1El3zOMCzOHMg2jMjX0TzPRPzNDM6sy_F1_M4czozPo1zK08XBjMvszIzOIvScziV0vMuBlfzOEfb8yESMzjMsy-BRbM1Mz3zJc6zKZ8zLXMyczGzPzMrjFezLLMl8zWzObMj8zUzPxcKsz8Isy-zIFiPztcPXjMrWbMpWHMxQDMkmPMt8yhEDlPAVlIzLwCGczpzJ5BBQ_M_8zezIIfzKUozKTM-czfzJA-zKnM1MyVWSXM2nfMtsyRWczuzKnM2cyCzPvMwilfzNHMnCzM0Mz3zJ0SKsySHczlfMzSe8z1zJgHzIvMzE0oXsy6U8yvzKdPLC9ezIBWzOnMwszszIzMzMzBzKLMgcyme8ybCszhzOjMzszkdszuWMyRzMo6zNvM7zIhzO3MzX03zMfMt0FLzIodQAUKzLpwbMzFzOIWzNsiXjJgzKg2IMyLzPrMg8ylWjrM6H_MmwzMqczCJcypUTpMzKnMhsyHzJbMkMzWzO3MuGPM-8yLzL3M6szRzJ1xI8ztzIUuE8yTzL96eCwqW1IjUXzMmMyloTPcASoGzO7M9XNkesy-zMZqd8yPHzRMORjMmcytKVNBzPbMo1przMRszKPM5GU1zJ3MjMywN0AfTcz6f3TMzMzGCszhPcyhAcy8SndgNszOKsz5zJYUaGkzLncxzPUszOdgzOR_zMHMjcyBQ8y2ORhQzKpXzIfMicywZhRJEEolWMynzOMdcxA9zMEtzJ1SzLLM_MyALyzMsszlzPkiGczszJ5qE8y4zOzMiyrMq17MvlcfzNbM5ARzzNkfHS_MugdyT8yfzPB-zKXMvTHMoxDM3A_MlsyhIMz7dczFNsyUecy8zL3M8FrM2cyXzOPMwMzhRgEcZkXM9szpU8zHVwrMz8yzNDMSWGvMvszWGMytzJRTBF3M8cyyzI8gzO3M2BEezIs6zPE5zLsBzK59eAp0zOseaMyNzMLMiBhaGMz5zIXMy8zIYBxuBTcUTA_MkAZsLszpzON1aFVeFznMv8zFAjJUFirMlszxzIHM3syuawgMXsyjVxFhCEsizKwrzMcyOn52zL3MokDM0TDM0sylzOTMtBrM9TnMpMzkzITMtczqzO3M_knMp8zWzMjM1MzsfV-ibWrcAErM8nTM5zosNMzqzJTMswBWzP9xzMHM6lINHHLMiXcvzPLMsXEJzMHM9syBHFg6zOfM38zrVszNzNjMsUEuzOnMhjbM0MzuzNB4VMyMzI_Mjg1szJdkc8zPzLsDzO0xzNHM0WZ6N8ywzN5rzJHMuczff6VhbHBoYdwBXQLMsHJpC8ysLczrGsziQEXMvkY6EyUyzPMCzInM8syWzMxTO8yCdxtozITMssydzMfM7syNPzRSCMzHzILMs3XMlnIzzNfM2WfMvczFzMBfzPxezJwdzKbMigJwzJnM8xFxzKjMuxNxZ8zJzNrM68yOL3TMriTMo3MrzKrMqszkK8zPOXkEzMPM_szEzLDM3MzizLnMxzovzJXMrXpMNMyczPBFzL3MhsyoHcyxWcyNzMDM3syrTMzkeg1wzNnM_MyHBszMdcy2zM49dHsTb8zJzPnMq8zjDkrM8316f8yCbsy4YcygzIHMmijM0MyEZsyzzL8GJ1t-S8ynBsz7D2ILbgTMwkxVaCXM2MzEzNXMjR7M5sykzMstJszOQBnM8nsWzPsqbVfMlDrM_8zSzOcgHsy9zMnMjGbMgcy4zJXM-GnM3GpnNHB-zNdfZUnM18yMzMA5zJLMk1ETci3MqnktzPR-zPtuPTlxIMzbLCDM-My0zNpbDcyBIAQpzPciImVcR2BNU8y9csz5zME_zPnMssy3zKTMpszHzLnMvxlvP8y0zNjM3nrMilotzLrM91bMpMyeESvMzRNszPpSzOxMF8yZFczjzJbMisygzMxFQDnMksytzMNHzItvEMypzNBgNR7Mwsz-zMQuzPB4c3AePV4ozOXMrMy9zK9nzKkcYcyVdDB6oXSFoTLcAQECS3crzL45zNbMgUxzY31AC8zHzL7MhnVAClB7JMy3WhzMjMzOzKHM4nnMhszQzNQYzPF5zJzM1syxzJzM9SPMoU7MrQrMoHbM1QLMisy_bczBfszqTMyizPvMylVubMyvzI3MrczXIWYOJ8zxzLfMkMzAEMyDHT3Mrg12HDk-zOTM_8z6C8yWUnPM38zDzLQsNcz_A0c2YczRKWzMu3fMsMyKzPnMw8yJzIrM8ivM88zcJcz8zI5wR8y_O8ygIMyTzJ1wG2lbHsy7zIIyGSpdT8zlYGMnfQYszKhFzOLMrgfMhSPMi8zeOF9ce28sIXLMnGnM9QTMgTJMdsyJzIxdzP4yDXY4zIpozJ10zNfMwMzBzJTMhMybzLzMjsyUSXNrzKpMG8z2fcyqzN7MsnXMi1p2J8yazINLzLPM_SlNzNMwLRldzNFtV8zUFcyGX8zpADxwc8yWzP_MmT_Mu8z1ZDxxM3NGzIx6zI3Mk8yiUMzxzLdszOB5oTHcAQBDc8zGzNhWK8zBORvMk3fM7DVxzM7Mi8zkzINTzJLMn8z9L39MzN4KbcyQBsypzPLMvsy6SMzLzNXMjx1ZP8zAzMTMm8zNKU9bUsy9GczTzL7MqsysKcyZUTwle3rMpczHeMyczOh0S8z5WcyGNG3M8MyGzJ_M3FZazOTM7y_M98zIVTJxPszyEy7M-mnM0sztzKDM4Cw0am1ECjTMzzAeVAXMtcymzNNmAszZMMyxzJ8BzI9VW8ySTR1zzKo8zNJJzJd-UGdTzKHM3WPM9MzDEszdfwrMk8y9S8z7zIw8zNRTAcyKEgp-zKZTzM_Mxn9YIcyENMzMzLnM68yxzNtxSxZPzMFONcyNzKg-zKIseEccJlvM6ytnzOjMmcztPRnM9CdYYcylM8zmzM_MxnbMpczEzNh-zOJQBhFdfczHFE3M-MyMD1VEzOEDCnh9zNI2Fz5TS8z0zNfMj0dzzPfMw1zMsEPMjcyEzKjMlCFEzK09bsz-oTDcAQECZFLM-gA1ay9GY3HM_g4cTnbMtmHMzxnMxcz7d8zzzLzMoGzM7MzrMczDBsyMOMy3zKPMjcy0P2TM2hlTBQnMo1XMuAHM1HFIzMPM9szLZsztf29rG8y2IjE1QGDMhMz9OFfMpsz1zP9ZzNLMkHTM4czZfXfM9CXM4gvMq33M4095zK01MAQBzMHMu3bMsMzKzOnMtVsWMhPMxzLMonHMyQbMl27M0wjM98zHNF5WIMy4KMzHzOHMsxXMlszfzJPMrcz0zObM28zgzPXMw8yMzKVTMwnM9y7M9Mz0zPPMpVnMiczKHDPMi8yazK_MoQjM8cybCGfMzj4RzLjM2zI7zNjMuArM2HZQzPkOzNLMskTM6cyCzMpUzNQxzKldIsz2THnM18z6JMz_J33M0czdWmU4NczozJHMhQvM6BhraczKWcyWzPggVz4_zJ4BJh0fzLXM2szdGMzWzMp2Lih-Ccy8zOTM7czEzN3MyszXzNHMoszCzOvMhsyozMBbzJhPEmGlREVMVEHcAQECWMzHHczNzJbM7czIRlTMkHUTI8zXzJ7M9FpDNMybNSHMjMyVzNwlzPPMjsy-YB8HzMQHJT7MsMy0zPASzPMXzIjM7syYcmPMu8yuDsyqzIE-zKMfzKrM6StBWThHI8yZzOVJRVvM5zTMrkMtMszVETXMgczYVTMaWGxGzKbMiDcizJYFGsyRzKVLzNrMmcyuSxLM-RV8zJ3M_GfMz8y2MsyyzK1czNTM2czazPFGGxUVzJ3M2DsjzIfM9RvMu8yAB8yGQEpNb8zQzIjMqylLzKnM2TwYzNgZzNUzzLpdzJQoB8yZzMo3PQbMni7MwlIlzOzMx2vMnHHM4gzMm8yCXMzEQVPMocyozL47PGfM7x1ZeMyfzLtZzITM4xhXzLLM1MyIzLIjzNHMyMyEzPxnXsyiQ3vMv8zPzJ8dzL5fzJPMgERkzK3MvXHM8MyNzMlNzO3MjXMSzK1wzNDM5nvMilXMixjMo8yxFHIjzMLMhypmzKzMgUw3zOQOzJhabcz7oTPcAQB2ejzMpcz3zNTMm0DMwWVJRmR9WnzM18zSzJRpzN8uzODMyczDzO8mbMzyzJQDMjLMwn3MsMyFzIfMlcyzYszlPCoEzO_MgcysW1fMoBjMzRITXhfMjMz1T2LMkcyJzKnM0BDMwMyeIszyzILM8sz1H8y3zKDMv8zHG2TM7cyjb8zqVsygzPgSzJzMusyVzKfMyMyxHMzGVWTM_A_M-sy8zNBYzIXMq8yezJ1nzKQvKMzKUjQ7zK9RzKpGzNxuVTFEzMDMxCI8zLNLzLRHzKFYWszpzOjMnCAHzIhrzNcJzLnMiczjzKcIIGRZzPnMgnZuP8ypQWwTzNJYWMz7zPUlW8zpRjDM9cz9OCTMmsyRAcy2M8zVH8yqI8yKQ0wgzKfM9szVUjPMh1JjBQvM5SrMnsypzMFozNnMssywzKbMgil7zN58zJQ0UVdSzK0ATMziClXMwAAGUcyDzI92GszpzJVbAsyzzMUdzIljH1XMo1M9zOI0fcz8zOHMmsyeZsz4qXByZWRpY2F0ZYOpYXR0cl9uYW1lsWJpcnRoZGF0ZV9kYXRlaW50pnBfdHlwZaJMRaV2YWx1Zc4BMhomr25vbl9yZXZvY19wcm9vZsA", + }, + "issuanceDate": "2024-07-10T13:32:18.596054178Z", + } + ], + "proof": { + "type": "DataIntegrityProof", + "proofPurpose": "authentication", + "verificationMethod": "JaAFqv91MMWh7Ne7aNPXCf:3:CL:1242241:faber.agent.degree_schema", + "challenge": "10275921379803826423", + "proofValue": "ukgOBqmFnZ3JlZ2F0ZWSCpmNfaGFzaNwAIAl7Bsy4zOAyNxZ9bsySX1fMh8zgWiRLBczrIVXMy8y3B8zuGczzzIrM6MzXzLGmY19saXN0ltwBAMy6zLFvNjHMxMyyzMQ5zKPM4FnM0Dw-XcyxRsyiZcypzPfMkDzMvszYzOXMgMyezPvMs8zjIR5reGpNTcz-zIzMjjlkzLfMiMzlzObMiMygzOfMxksPzN8SzJTMocyaaD4KYCbMsldNenPMsFQlzKZVJsy2DF1sSlnMjWjMnWnMpisBzODMw0HMhcz2YVLMjALM41rMucybbVEWOzXMmMzizPwEzPoXzIPMrlfMzcywNxhlO2xAzILM6xLMhczHzOITHMzgzO_MhczTzM5wT8ylzNQxeDzM8My3zM0FzLIyzK0pfQXM88yUzMINzKjM-MyuccyERj8nZyXMkTnM_2_M2Mz2QcyWD8z1zIvM6HdzXwEpzOjM0MyiY8z6EsyYzNrMzczdWkDMrsymzPRizOReJzo1HFXMv2HM9EpsE8zHzODMrH_Mvz3MxsyyN1_MlHAHDg5_FE7M1MztzKoGCRbM02pvzLbM98yjPMzKzOk5zJHMpBs4zKpNNMyPWNwBAQJkUsz6ADVrL0Zjccz-DhxOdsy2YczPGczFzPt3zPPMvMygbMzszOsxzMMGzIw4zLfMo8yNzLQ_ZMzaGVMFCcyjVcy4AczUcUjMw8z2zMtmzO1_b2sbzLYiMTVAYMyEzP04V8ymzPXM_1nM0syQdMzhzNl9d8z0JcziC8yrfczjT3nMrTUwBAHMwcy7dsywzMrM6cy1WxYyE8zHMsyicczJBsyXbszTCMz3zMc0XlYgzLgozMfM4cyzFcyWzN_Mk8ytzPTM5szbzODM9czDzIzMpVMzCcz3Lsz0zPTM88ylWcyJzMocM8yLzJrMr8yhCMzxzJsIZ8zOPhHMuMzbMjvM2My4CszYdlDM-Q7M0syyRMzpzILMylTM1DHMqV0izPZMeczXzPokzP8nfczRzN1aZTg1zOjMkcyFC8zoGGtpzMpZzJbM-CBXPj_MngEmHR_MtczazN0YzNbMynYuKH4JzLzM5MztzMTM3czKzNfM0cyizMLM68yGzKjMwFvMmE8SYdwBAENzzMbM2FYrzME5G8yTd8zsNXHMzsyLzOTMg1PMksyfzP0vf0zM3gptzJAGzKnM8sy-zLpIzMvM1cyPHVk_zMDMxMybzM0pT1tSzL0ZzNPMvsyqzKwpzJlRPCV7esylzMd4zJzM6HRLzPlZzIY0bczwzIbMn8zcVlrM5MzvL8z3zMhVMnE-zPITLsz6aczSzO3MoMzgLDRqbUQKNMzPMB5UBcy1zKbM02YCzNkwzLHMnwHMj1VbzJJNHXPMqjzM0knMl35QZ1PMoczdY8z0zMMSzN1_CsyTzL1LzPvMjDzM1FMBzIoSCn7MplPMz8zGf1ghzIQ0zMzMuczrzLHM23FLFk_MwU41zI3MqD7Moix4RxwmW8zrK2fM6MyZzO09Gcz0J1hhzKUzzObMz8zGdsylzMTM2H7M4lAGEV19zMcUTcz4zIwPVUTM4QMKeH3M0jYXPlNLzPTM18yPR3PM98zDXMywQ8yNzITMqMyUIUTMrT1uzP7cAQECS3crzL45zNbMgUxzY31AC8zHzL7MhnVAClB7JMy3WhzMjMzOzKHM4nnMhszQzNQYzPF5zJzM1syxzJzM9SPMoU7MrQrMoHbM1QLMisy_bczBfszqTMyizPvMylVubMyvzI3MrczXIWYOJ8zxzLfMkMzAEMyDHT3Mrg12HDk-zOTM_8z6C8yWUnPM38zDzLQsNcz_A0c2YczRKWzMu3fMsMyKzPnMw8yJzIrM8ivM88zcJcz8zI5wR8y_O8ygIMyTzJ1wG2lbHsy7zIIyGSpdT8zlYGMnfQYszKhFzOLMrgfMhSPMi8zeOF9ce28sIXLMnGnM9QTMgTJMdsyJzIxdzP4yDXY4zIpozJ10zNfMwMzBzJTMhMybzLzMjsyUSXNrzKpMG8z2fcyqzN7MsnXMi1p2J8yazINLzLPM_SlNzNMwLRldzNFtV8zUFcyGX8zpADxwc8yWzP_MmT_Mu8z1ZDxxM3NGzIx6zI3Mk8yiUMzxzLdszOB53AEAdno8zKXM98zUzJtAzMFlSUZkfVp8zNfM0syUaczfLszgzMnMw8zvJmzM8syUAzIyzMJ9zLDMhcyHzJXMs2LM5TwqBMzvzIHMrFtXzKAYzM0SE14XzIzM9U9izJHMicypzNAQzMDMniLM8syCzPLM9R_Mt8ygzL_MxxtkzO3Mo2_M6lbMoMz4EsyczLrMlcynzMjMsRzMxlVkzPwPzPrMvMzQWMyFzKvMnsydZ8ykLyjMylI0O8yvUcyqRszcblUxRMzAzMQiPMyzS8y0R8yhWFrM6czozJwgB8yIa8zXCcy5zInM48ynCCBkWcz5zIJ2bj_MqUFsE8zSWFjM-8z1JVvM6UYwzPXM_TgkzJrMkQHMtjPM1R_MqiPMikNMIMynzPbM1VIzzIdSYwULzOUqzJ7MqczBaMzZzLLMsMymzIIpe8zefMyUNFFXUsytAEzM4gpVzMAABlHMg8yPdhrM6cyVWwLMs8zFHcyJYx9VzKNTPcziNH3M_MzhzJrMnmbM-NwBAQJYzMcdzM3MlsztzMhGVMyQdRMjzNfMnsz0WkM0zJs1IcyMzJXM3CXM88yOzL5gHwfMxAclPsywzLTM8BLM8xfMiMzuzJhyY8y7zK4OzKrMgT7Mox_MqszpK0FZOEcjzJnM5UlFW8znNMyuQy0yzNURNcyBzNhVMxpYbEbMpsyINyLMlgUazJHMpUvM2syZzK5LEsz5FXzMncz8Z8zPzLYyzLLMrVzM1MzZzNrM8UYbFRXMnczYOyPMh8z1G8y7zIAHzIZASk1vzNDMiMyrKUvMqczZPBjM2BnM1TPMul3MlCgHzJnMyjc9BsyeLszCUiXM7MzHa8ycccziDMybzIJczMRBU8yhzKjMvjs8Z8zvHVl4zJ_Mu1nMhMzjGFfMsszUzIjMsiPM0czIzITM_GdezKJDe8y_zM_Mnx3Mvl_Mk8yARGTMrcy9cczwzI3MyU3M7cyNcxLMrXDM0Mzme8yKVcyLGMyjzLEUciPMwsyHKmbMrMyBTDfM5A7MmFptzPs", + "cryptosuite": "anoncreds-2023", + }, + "presentation_submission": { + "id": "bd3db08f-29b0-4f62-843b-91919f331d19", + "definition_id": "5591656f-5b5d-40f8-ab5c-9041c8e3a6a0", + "descriptor_map": [ + { + "id": "age-verification", + "format": "ldp_vc", + "path": "$.verifiableCredential[0]", + } + ], + }, +} + + +@pytest.fixture +def resolver(): + yield DIDResolver([KeyDIDResolver()]) + + +@pytest.fixture +def profile(resolver: DIDResolver): + profile = InMemoryProfile.test_profile( + {}, + { + DIDMethods: DIDMethods(), + BaseVerificationKeyStrategy: DefaultVerificationKeyStrategy(), + DIDResolver: resolver, + }, + ) + profile.context.injector.bind_instance(DocumentLoader, DocumentLoader(profile)) + yield profile + + +@pytest.fixture +def manager(profile: Profile): + yield VcDiManager(profile) + + +@pytest.mark.asyncio +async def test_assert_no_callenge_error(manager: VcDiManager): + with pytest.raises(VcDiManagerError) as context: + + await manager.verify_presentation({}, {"options": {}}) + + +@pytest.mark.asyncio +async def test_assert_verify_presentation(manager: VcDiManager, profile: Profile): + profile.context.injector.bind_instance( + AnonCredsRegistry, + mock.MagicMock( + get_schema=mock.CoroutineMock( + return_value=mock.MagicMock( + schema=mock.MagicMock( + serialize=mock.MagicMock( + return_value={ + "issuerId": ISSUER_ID, + "attrNames": [ + "degree", + "name", + "date", + "birthdate_dateint", + "timestamp", + ], + "name": "degree schema", + "version": "68.37.38", + } + ) + ) + ), + ), + get_credential_definition=mock.CoroutineMock( + return_value=mock.MagicMock( + credential_definition=mock.MagicMock( + serialize=mock.MagicMock( + return_value={ + "issuerId": ISSUER_ID, + "schemaId": "1242274", + "type": "CL", + "tag": "faber.agent.degree_schema", + "value": { + "primary": { + "n": "110191895107383177944225186787127045472400456419044508847920073749118325816882879555888693590503408107531218246551508200778858813446764597252042460295740219285296989541107769089502181778576777417930436325553990675031577262316859960188786027427415918907113489291521324039356114616863101480866513302357191568489145237650236954578848085540776058357840327446186761324850043360872510240168860114128657413033422322367730714244593033343815255367880227269514752281182456165390304117532268729405376542376337985308310279351286237047011259755660096523481882637528390140070572293678914392133356847907443964699396666368223294158061", + "s": "9415812539608195450966883098870652296916491635505203853080983952632805384302967432306788150605939803147875077062094749099736966082292346688785945337831356656890556951983186155154365222554857316130536164238857088110797546973073757951768643832909466949503368156244645826883930998004092294090171380523954224832439522626754713977466892117127635439861663437421962242296372559287964629293301712321588335521469805244310171886131183734977508209622245349508899886419257943124600270414702700150921447687226557458680951807835253145114264110732291146813141269571018474399690170085303553149912953395770110046436238930438812780147", + "r": { + "birthdate_dateint": "39968681640304788380009477273748351837529664198759676077165379337792646675901616432152370797900826515094147804138445249213613945496792933661400605498715980120831712924000357777147499418810050923414071460841739245157420435453468918475700941287524373324079549579873246517784186882218530939272891114746579591872034727864365855842228114509998007945460010480245507010064392110362136591952150320820422225826544285688677068642300714078641169340591902569248592823824054227933825730852985690007682552923384746004484874059862968040467406436797474876079422807683482900156405231629390843729772887675489043282470807407031205186969", + "date": "1189483679228280739072650972354053068606455987118485184085207434597201188158334235777355692068087062063721870127753534657818335769737924323208415920434077874887496282538032440486476302782516445945959644735720202525994434469144049628428271543868582987595621454107048590112351992445017094207270819619996701213546548131486236291413202271720647291688756231888188593794699964380031588638888843772851461391755221206371940247603348559507718057920345380550594583486893575953718560696958190724499821774481967602504818394556262808918356126374081669920344874855870303258253201308390418654588788321421245107716899898452672677669", + "degree": "7930507600973622255761968698285828306846788291380201483787889093576185243433070380250482080700646211095178134232253270069460369492303935894012408118127081015494559256719242506733590055152439637360319677447051969350390638377492011800932771226273035358153040455317319159092480932337395314046192165987582330687751669998427678060271101315462477794016231639142468258481489055468781240927905148904953855558036223028111290216890655943559980456204339868018478633494472724669957163721224817260017913056937034080193667933454564153001308165996679854735603451224195324968916426779531862123459043377650779177644556309703126058569", + "master_secret": "276481248076029901397915904197571939766835573218052607192738620704710670905748562925445909482694999303080385244124333488797597869947177554955868568791587429441803954589662661470453559603193697529455695636193068394109938416082954992003652270737665467573503772195889460315616035410445104017129549757818399000451038212677658874397388131875030264607848961441095008175483303770912033940379028110558590125358672540783519319152844457014577027814244423449014637405165845051430105495276716743166413876676529214061455612429316323214502293979149015655581759299623912651153921930483011898615547608936905586870150072549489722497", + "name": "82868656041023829484169603686526910742809130312561664067502441442293881789487387499604367473067725155834966664040618821809499511114213661657225202206321891616189181748382445256076664775972124872336589185892368887947537027000430124719570575062210504764164198906984696516109885071145245277063304337918433238788453702459065343194907264612324316538889460640366702448456177503504044952464912882387589287029922982523474901886018581567364020613890822736197654463892632953961461903659266302672901927472391491520220790086494421629530104134487848960992291400448418880719301301643211538480715261724678645854026893882662935786454", + "timestamp": "7031267523384255931724229734709668852236981666398664500628609312084184839422714066646111517571504367881906999946906142205880431579116282139457459430490628368074988968893818537384306625760198775803846638688715282693724689063695143781851672316171826804538178049496230705683899355140338093894078404749873276975725352612375324676593660719462779985715395422238940426215008469041353116398836478202492841783866649731302994305254257899941902815383605743664381990123198126148381917220067404201702094151978496034163563512821780561363033330132250212505439974465744473015256287665802141980858496388564369684473390614913213492963", + }, + "rctxt": "57595457065224964384532723809214070177221283698619189163273994795484340422910421565194502650047793930986106754378617657953634148479003200343596849944404829819454759272470409952906484645890312928698071525795674306099322535554040769358088959541527217412947857624113771388855906636033132647358149811523979540215755447957501980890442007680654813076936017670559128454293737368578572852087014890823425797572658699153814350307873126659084777516770578065919757432552875676249519945052600028967301682399735687262474002867432540942797659023173846671235714105519229325988649284822140546012838121486756140885474564667338033508925", + "z": "92392248633579159734489059262157658066602172492655376458258465743015720279931884516580613066265472490833888951133646441506435308647071645829490880133890938400531213056849488446865347760761739826520600426315831962473284037732716361460703303047650224879262380737182724193613502180709449361207809530289742931388401783902150303407116160750968583373664158158828048355594343560019377499225431222258609781090866038688202681876195069528239050879910722452167883110680950451692711483330249956590291961929310759534360045884090140017202751268354570741889324005873479145636960045391944928193003593085006050343477314073342608326752", + } + }, + } + ) + ) + ) + ), + ), + ) + + # TODO: this mock should removed + with mock.patch.object(W3cPresentation, "verify", return_value=True): + vp = await manager.verify_presentation( + VerifiablePresentation.deserialize(VP), OPTIONS + ) + + assert vp.verified and vp.errors == [] diff --git a/aries_cloudagent/vc/vc_di/tests/test_prove.py b/aries_cloudagent/vc/vc_di/tests/test_prove.py new file mode 100644 index 0000000000..29df71df5a --- /dev/null +++ b/aries_cloudagent/vc/vc_di/tests/test_prove.py @@ -0,0 +1,319 @@ +"""test prove.py""" + +import pytest +from aries_cloudagent.anoncreds.holder import AnonCredsHolder, AnonCredsHolderError +from aries_cloudagent.anoncreds.tests.mock_objects import MOCK_W3CPRES +from aries_cloudagent.revocation.models.revocation_registry import RevocationRegistry +from aries_cloudagent.vc.ld_proofs.error import LinkedDataProofException +from aries_cloudagent.anoncreds.registry import AnonCredsRegistry +from aries_cloudagent.tests import mock +from ....core.in_memory.profile import InMemoryProfile +from ....core.profile import Profile +from ....resolver.default.key import KeyDIDResolver +from ....resolver.did_resolver import DIDResolver +from ....wallet.default_verification_key_strategy import ( + BaseVerificationKeyStrategy, + DefaultVerificationKeyStrategy, +) +from ....wallet.did_method import DIDMethods +from ...ld_proofs.document_loader import DocumentLoader + +from ..prove import ( + _extract_cred_idx, + _get_predicate_type_and_value, + _load_w3c_credentials, + create_rev_states, + create_signed_anoncreds_presentation, +) +from .test_manager import VC +from anoncreds import RevocationStatusList, CredentialRevocationState + + +@pytest.fixture +def resolver(): + yield DIDResolver([KeyDIDResolver()]) + + +@pytest.fixture +def profile(resolver: DIDResolver): + profile = InMemoryProfile.test_profile( + {}, + { + DIDMethods: DIDMethods(), + BaseVerificationKeyStrategy: DefaultVerificationKeyStrategy(), + DIDResolver: resolver, + }, + ) + profile.context.injector.bind_instance(DocumentLoader, DocumentLoader(profile)) + yield profile + + +@pytest.mark.asyncio +async def test_create_signed_anoncreds_presentation(profile: Profile): + profile.context.injector.bind_instance( + AnonCredsRegistry, + mock.MagicMock( + get_schema=mock.CoroutineMock( + return_value=mock.MagicMock( + schema=mock.MagicMock( + serialize=mock.MagicMock( + return_value={ + "issuerId": "TNuyNH2pAW2G6z3BW8ZYLf", + "attrNames": [ + "degree", + "name", + "date", + "birthdate_dateint", + "timestamp", + ], + "name": "degree schema", + "version": "68.37.38", + } + ) + ) + ), + ), + get_credential_definition=mock.CoroutineMock( + return_value=mock.MagicMock( + credential_definition=mock.MagicMock( + serialize=mock.MagicMock( + return_value={ + "issuerId": "TNuyNH2pAW2G6z3BW8ZYLf", + "schemaId": "1242274", + "type": "CL", + "tag": "faber.agent.degree_schema", + "value": { + "primary": { + "n": "110191895107383177944225186787127045472400456419044508847920073749118325816882879555888693590503408107531218246551508200778858813446764597252042460295740219285296989541107769089502181778576777417930436325553990675031577262316859960188786027427415918907113489291521324039356114616863101480866513302357191568489145237650236954578848085540776058357840327446186761324850043360872510240168860114128657413033422322367730714244593033343815255367880227269514752281182456165390304117532268729405376542376337985308310279351286237047011259755660096523481882637528390140070572293678914392133356847907443964699396666368223294158061", + "s": "9415812539608195450966883098870652296916491635505203853080983952632805384302967432306788150605939803147875077062094749099736966082292346688785945337831356656890556951983186155154365222554857316130536164238857088110797546973073757951768643832909466949503368156244645826883930998004092294090171380523954224832439522626754713977466892117127635439861663437421962242296372559287964629293301712321588335521469805244310171886131183734977508209622245349508899886419257943124600270414702700150921447687226557458680951807835253145114264110732291146813141269571018474399690170085303553149912953395770110046436238930438812780147", + "r": { + "birthdate_dateint": "39968681640304788380009477273748351837529664198759676077165379337792646675901616432152370797900826515094147804138445249213613945496792933661400605498715980120831712924000357777147499418810050923414071460841739245157420435453468918475700941287524373324079549579873246517784186882218530939272891114746579591872034727864365855842228114509998007945460010480245507010064392110362136591952150320820422225826544285688677068642300714078641169340591902569248592823824054227933825730852985690007682552923384746004484874059862968040467406436797474876079422807683482900156405231629390843729772887675489043282470807407031205186969", + "date": "1189483679228280739072650972354053068606455987118485184085207434597201188158334235777355692068087062063721870127753534657818335769737924323208415920434077874887496282538032440486476302782516445945959644735720202525994434469144049628428271543868582987595621454107048590112351992445017094207270819619996701213546548131486236291413202271720647291688756231888188593794699964380031588638888843772851461391755221206371940247603348559507718057920345380550594583486893575953718560696958190724499821774481967602504818394556262808918356126374081669920344874855870303258253201308390418654588788321421245107716899898452672677669", + "degree": "7930507600973622255761968698285828306846788291380201483787889093576185243433070380250482080700646211095178134232253270069460369492303935894012408118127081015494559256719242506733590055152439637360319677447051969350390638377492011800932771226273035358153040455317319159092480932337395314046192165987582330687751669998427678060271101315462477794016231639142468258481489055468781240927905148904953855558036223028111290216890655943559980456204339868018478633494472724669957163721224817260017913056937034080193667933454564153001308165996679854735603451224195324968916426779531862123459043377650779177644556309703126058569", + "master_secret": "276481248076029901397915904197571939766835573218052607192738620704710670905748562925445909482694999303080385244124333488797597869947177554955868568791587429441803954589662661470453559603193697529455695636193068394109938416082954992003652270737665467573503772195889460315616035410445104017129549757818399000451038212677658874397388131875030264607848961441095008175483303770912033940379028110558590125358672540783519319152844457014577027814244423449014637405165845051430105495276716743166413876676529214061455612429316323214502293979149015655581759299623912651153921930483011898615547608936905586870150072549489722497", + "name": "82868656041023829484169603686526910742809130312561664067502441442293881789487387499604367473067725155834966664040618821809499511114213661657225202206321891616189181748382445256076664775972124872336589185892368887947537027000430124719570575062210504764164198906984696516109885071145245277063304337918433238788453702459065343194907264612324316538889460640366702448456177503504044952464912882387589287029922982523474901886018581567364020613890822736197654463892632953961461903659266302672901927472391491520220790086494421629530104134487848960992291400448418880719301301643211538480715261724678645854026893882662935786454", + "timestamp": "7031267523384255931724229734709668852236981666398664500628609312084184839422714066646111517571504367881906999946906142205880431579116282139457459430490628368074988968893818537384306625760198775803846638688715282693724689063695143781851672316171826804538178049496230705683899355140338093894078404749873276975725352612375324676593660719462779985715395422238940426215008469041353116398836478202492841783866649731302994305254257899941902815383605743664381990123198126148381917220067404201702094151978496034163563512821780561363033330132250212505439974465744473015256287665802141980858496388564369684473390614913213492963", + }, + "rctxt": "57595457065224964384532723809214070177221283698619189163273994795484340422910421565194502650047793930986106754378617657953634148479003200343596849944404829819454759272470409952906484645890312928698071525795674306099322535554040769358088959541527217412947857624113771388855906636033132647358149811523979540215755447957501980890442007680654813076936017670559128454293737368578572852087014890823425797572658699153814350307873126659084777516770578065919757432552875676249519945052600028967301682399735687262474002867432540942797659023173846671235714105519229325988649284822140546012838121486756140885474564667338033508925", + "z": "92392248633579159734489059262157658066602172492655376458258465743015720279931884516580613066265472490833888951133646441506435308647071645829490880133890938400531213056849488446865347760761739826520600426315831962473284037732716361460703303047650224879262380737182724193613502180709449361207809530289742931388401783902150303407116160750968583373664158158828048355594343560019377499225431222258609781090866038688202681876195069528239050879910722452167883110680950451692711483330249956590291961929310759534360045884090140017202751268354570741889324005873479145636960045391944928193003593085006050343477314073342608326752", + } + }, + } + ) + ) + ) + ), + ), + ) + + with mock.patch.object( + AnonCredsHolder, "create_presentation_w3c", return_value=MOCK_W3CPRES + ) as mock_create_pres: + await create_signed_anoncreds_presentation( + profile=profile, + pres_definition={ + "id": "5591656f-5b5d-40f8-ab5c-9041c8e3a6a0", + "name": "Age Verification", + "purpose": "We need to verify your age before entering a bar", + "format": { + "di_vc": { + "proof_type": ["DataIntegrityProof"], + "cryptosuite": ["anoncreds-2023", "eddsa-rdfc-2022"], + } + }, + "input_descriptors": [ + { + "id": "age-verification", + "name": "A specific type of VC + Issuer", + "purpose": "We want a VC of this type generated by this issuer", + "constraints": { + "limit_disclosure": "required", + "fields": [ + { + "path": ["$.issuer"], + "filter": { + "type": "string", + "const": "7yDP6qARVAp1Rims8Fj43k", + }, + }, + {"path": ["$.credentialSubject.name"]}, + {"path": ["$.credentialSubject.degree"]}, + { + "path": ["$.credentialSubject.birthdate_dateint"], + "predicate": "preferred", + "filter": {"type": "number", "maximum": 20060711}, + }, + ], + "statuses": { + "active": {"directive": "disallowed"}, + "suspended": {"directive": None}, + "revoked": {"directive": None}, + }, + }, + "schema": { + "uri_groups": [ + [ + { + "uri": "https://www.w3.org/2018/credentials#VerifiableCredential" + } + ] + ], + "oneof_filter": False, + }, + } + ], + }, + presentation={ + "@context": ["https://www.w3.org/2018/credentials/v1"], + "type": ["VerifiablePresentation"], + "verifiableCredential": [ + { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/security/data-integrity/v2", + { + "@vocab": "https://www.w3.org/ns/credentials/issuer-dependent#" + }, + ], + "type": ["VerifiableCredential"], + "issuer": "7yDP6qARVAp1Rims8Fj43k", + "issuanceDate": "2024-07-11T18:33:24.564345117Z", + "credentialSubject": { + "name": "Alice Smith", + "date": "2018-05-28", + "timestamp": 1720722800, + "degree": "Maths", + "birthdate_dateint": 20000711, + }, + "proof": { + "type": "DataIntegrityProof", + "proofPurpose": "assertionMethod", + "verificationMethod": "7yDP6qARVAp1Rims8Fj43k:3:CL:1255455:faber.agent.degree_schema", + "proofValue": "ukgGEqXNjaGVtYV9pZNkvN3lEUDZxQVJWQXAxUmltczhGajQzazoyOmRlZ3JlZSBzY2hlbWE6NDYuMzkuNzmrY3JlZF9kZWZfaWTZPTd5RFA2cUFSVkFwMVJpbXM4Rmo0M2s6MzpDTDoxMjU1NDU1OmZhYmVyLmFnZW50LmRlZ3JlZV9zY2hlbWGpc2lnbmF0dXJlgqxwX2NyZWRlbnRpYWyEo21fMtwAIFHM9cyPzOMsEls6E3zMtC0BaMzTGsyazNzMnsz2zL9lzJHMj8zHaczATsyUYi7Mv6Fh3AEBAsy8zMrMwFHM5MzHzPTMxQc-zPjM68ywV8ypXszEzJLMv8yaNczYDhvMqR7M9CzMv8yweszIccyqCWHMiwbMrGrMyRU0zOTM2sypBczfzII7VksbBMzXTRzMyhjM2idFNsyCBszDOMzVzLDM3cy8IVDMy0YETSfM_VFwzPfM-gdaAcyUNBJ8zPxgeszLO8zwzNjM98zPNFgKBRXM_mZ4zIXMnszOzOjMscyNJQTMmszjD3drzMXMgSBAzJ_M8MzkawYXzP7MrszKzLJPzPXMjEd8HsyzEczBzNEmzJ7Mkl_Mmcz0csziGk9eVD9ezMEfchjMzsyFJsyhQj_MrczyzLDMtWYczIorzMULzPbM0mUSzP1aR8zvzJQRQcyuzP1czK8TDQxSK8zjzOZ8zKIFUcyXzPZpdk9PzLpLNczzHyTM7SU5zJQFRcyaWAdcWczodSfMkszKAmLM7igjeMz_zN3Ms8zAT0TM2cyYJmEqzODM20rM8klTzPChZdwASxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMozMsWzN3M8MzSDg3MhsyTzLRLzIwzoXbcAVUMzM3MlCDMhMyMH1LMigd7JEUHIMzuW8yQJT8iAm7Mpw5nAsyTzKbMtsyUzPUzzK9FzLLMkFLMzynMu8yEzN5nO8z3FjvMiszIzJbM3cywWMzTEszQzPzMx8yNBcycexcle8yIzJfMycyTQszUMBTMg8zGzNPMisz3zLhnEszDdcyozLXM1TYFzPRCO8zfcSQLNGPMoszyf8yfCwtVzNbM18zJzNDMscyzzJbM3l3Mh8zPzKYhCcyXzIrM98ykzODMiczSzJPMgRXMgnDMhkLMhCLMkwRkzKotF8yDzPbMmGzMoC4LRMyZzJ3M8w7MvczHLcyzzMTMmCdJzILMwUHM98zMzPpSDyLM5X7M2kXMg8yQXHvM8MyqV0nMmAgQzKx9LTM2V8zlzLLMv8z8zPJqTMzjR8zLFlU2KFRHc8zXzOfMrMzSdArM7CDMksykzM3MjMznLGo-zLzM7QVqzPXM6Bl9aMzgzIsSzLlpNMyYa8z5zId1BnNyzPUxZczyX8zSzNYKfMzIzJPM2QvM_MzIzLvMvRw4ZgwgzLkvGszAKszmMMzfzLbM4czQRGfM21Z5PzsxzIRlzJETzNszMMydzOdAzObMuczJzJUZzKgXzJ_MlG0PDcz4QCXMpwDMyszXzPMVzOFrzKjM3hc6zP_M-sz3XMyfS1vMvF_Mlsz7MMzozKzMk8y0rHJfY3JlZGVudGlhbMC7c2lnbmF0dXJlX2NvcnJlY3RuZXNzX3Byb29mgqJzZdwBAMy0zO7MyszkzN7MxcyVQMy3zNEdzKBvzMfMq2Q5agJwG8yazPjMpMyQzJ1JV8z_zOrMkMzWzKPMjcybPMyHX8yVzMDM-WBwBcz6zN0yHRbMnSDMoMyXHMynzOfMlMyiCnfM-Mylb8zKzPBHzMrMm1TM2l7Mp8zCQMyyzIYHzMBXzPJFf04lzPpYzKLM7sykKMzHzJgJzNoTJEVtzP5KzJ7Mz8yAXUPM-13M18yQWhpOXszRFczqzI3MqszDJ8yjE2V-zLxUzPpDUknMrh_Mu0sVzJBAzMgqfX1ZF8zRzLzMr8zIzKtdeRbM4szqCFnMqczXdsyIPMzXzK_MhQTM5sypzOrM5MziHlXMxxrMpMzTzPjMjXbMzEZxNsykzJRKFjXM-XtYzIcEzJzMgMynzITMyWxpEix9zOvM1cy5ZcyacMzJLMzxzN9OzM3MuV7M28z7zKQRHsyXzPDMvczhB8zgzNrMmxwhJMydEhPMhUkvzIHM3cytzL8CIiLMtczJzOLMlMyZOMzOzLcizOwAoWPcACAvzI_MrkTMvDvMmMy2zIvMizDM0ihSzMd_VGDMvMzEWn3MrczcbMykzKNqzLgzzKvMzw", + "cryptosuite": "anoncreds-2023", + }, + } + ], + "presentation_submission": { + "id": "700e4ed4-bb73-403f-a73c-e456556cbdf0", + "definition_id": "5591656f-5b5d-40f8-ab5c-9041c8e3a6a0", + "descriptor_map": [ + { + "id": "age-verification", + "format": "ldp_vc", + "path": "$.verifiableCredential[0]", + } + ], + }, + }, + challenge="3fa85f64-5717-4562-b3fc-2c963f66afa7", + domain="domain", + credentials=[ + { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/security/data-integrity/v2", + { + "@vocab": "https://www.w3.org/ns/credentials/issuer-dependent#" + }, + ], + "type": ["VerifiableCredential"], + "issuer": "7yDP6qARVAp1Rims8Fj43k", + "issuanceDate": "2024-07-11T18:33:24.564345117Z", + "credentialSubject": { + "name": "Alice Smith", + "date": "2018-05-28", + "timestamp": 1720722800, + "degree": "Maths", + "birthdate_dateint": 20000711, + }, + "proof": { + "type": "DataIntegrityProof", + "proofPurpose": "assertionMethod", + "verificationMethod": "7yDP6qARVAp1Rims8Fj43k:3:CL:1255455:faber.agent.degree_schema", + "proofValue": "ukgGEqXNjaGVtYV9pZNkvN3lEUDZxQVJWQXAxUmltczhGajQzazoyOmRlZ3JlZSBzY2hlbWE6NDYuMzkuNzmrY3JlZF9kZWZfaWTZPTd5RFA2cUFSVkFwMVJpbXM4Rmo0M2s6MzpDTDoxMjU1NDU1OmZhYmVyLmFnZW50LmRlZ3JlZV9zY2hlbWGpc2lnbmF0dXJlgqxwX2NyZWRlbnRpYWyEo21fMtwAIFHM9cyPzOMsEls6E3zMtC0BaMzTGsyazNzMnsz2zL9lzJHMj8zHaczATsyUYi7Mv6Fh3AEBAsy8zMrMwFHM5MzHzPTMxQc-zPjM68ywV8ypXszEzJLMv8yaNczYDhvMqR7M9CzMv8yweszIccyqCWHMiwbMrGrMyRU0zOTM2sypBczfzII7VksbBMzXTRzMyhjM2idFNsyCBszDOMzVzLDM3cy8IVDMy0YETSfM_VFwzPfM-gdaAcyUNBJ8zPxgeszLO8zwzNjM98zPNFgKBRXM_mZ4zIXMnszOzOjMscyNJQTMmszjD3drzMXMgSBAzJ_M8MzkawYXzP7MrszKzLJPzPXMjEd8HsyzEczBzNEmzJ7Mkl_Mmcz0csziGk9eVD9ezMEfchjMzsyFJsyhQj_MrczyzLDMtWYczIorzMULzPbM0mUSzP1aR8zvzJQRQcyuzP1czK8TDQxSK8zjzOZ8zKIFUcyXzPZpdk9PzLpLNczzHyTM7SU5zJQFRcyaWAdcWczodSfMkszKAmLM7igjeMz_zN3Ms8zAT0TM2cyYJmEqzODM20rM8klTzPChZdwASxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMozMsWzN3M8MzSDg3MhsyTzLRLzIwzoXbcAVUMzM3MlCDMhMyMH1LMigd7JEUHIMzuW8yQJT8iAm7Mpw5nAsyTzKbMtsyUzPUzzK9FzLLMkFLMzynMu8yEzN5nO8z3FjvMiszIzJbM3cywWMzTEszQzPzMx8yNBcycexcle8yIzJfMycyTQszUMBTMg8zGzNPMisz3zLhnEszDdcyozLXM1TYFzPRCO8zfcSQLNGPMoszyf8yfCwtVzNbM18zJzNDMscyzzJbM3l3Mh8zPzKYhCcyXzIrM98ykzODMiczSzJPMgRXMgnDMhkLMhCLMkwRkzKotF8yDzPbMmGzMoC4LRMyZzJ3M8w7MvczHLcyzzMTMmCdJzILMwUHM98zMzPpSDyLM5X7M2kXMg8yQXHvM8MyqV0nMmAgQzKx9LTM2V8zlzLLMv8z8zPJqTMzjR8zLFlU2KFRHc8zXzOfMrMzSdArM7CDMksykzM3MjMznLGo-zLzM7QVqzPXM6Bl9aMzgzIsSzLlpNMyYa8z5zId1BnNyzPUxZczyX8zSzNYKfMzIzJPM2QvM_MzIzLvMvRw4ZgwgzLkvGszAKszmMMzfzLbM4czQRGfM21Z5PzsxzIRlzJETzNszMMydzOdAzObMuczJzJUZzKgXzJ_MlG0PDcz4QCXMpwDMyszXzPMVzOFrzKjM3hc6zP_M-sz3XMyfS1vMvF_Mlsz7MMzozKzMk8y0rHJfY3JlZGVudGlhbMC7c2lnbmF0dXJlX2NvcnJlY3RuZXNzX3Byb29mgqJzZdwBAMy0zO7MyszkzN7MxcyVQMy3zNEdzKBvzMfMq2Q5agJwG8yazPjMpMyQzJ1JV8z_zOrMkMzWzKPMjcybPMyHX8yVzMDM-WBwBcz6zN0yHRbMnSDMoMyXHMynzOfMlMyiCnfM-Mylb8zKzPBHzMrMm1TM2l7Mp8zCQMyyzIYHzMBXzPJFf04lzPpYzKLM7sykKMzHzJgJzNoTJEVtzP5KzJ7Mz8yAXUPM-13M18yQWhpOXszRFczqzI3MqszDJ8yjE2V-zLxUzPpDUknMrh_Mu0sVzJBAzMgqfX1ZF8zRzLzMr8zIzKtdeRbM4szqCFnMqczXdsyIPMzXzK_MhQTM5sypzOrM5MziHlXMxxrMpMzTzPjMjXbMzEZxNsykzJRKFjXM-XtYzIcEzJzMgMynzITMyWxpEix9zOvM1cy5ZcyacMzJLMzxzN9OzM3MuV7M28z7zKQRHsyXzPDMvczhB8zgzNrMmxwhJMydEhPMhUkvzIHM3cytzL8CIiLMtczJzOLMlMyZOMzOzLcizOwAoWPcACAvzI_MrkTMvDvMmMy2zIvMizDM0ihSzMd_VGDMvMzEWn3MrczcbMykzKNqzLgzzKvMzw", + "cryptosuite": "anoncreds-2023", + }, + } + ], + purpose="assertionMethod", + ) + + mock_create_pres.assert_called_once() + + +def test__extract_cred_idx(): + item_path = "$.verifiableCredential[0]" + assert _extract_cred_idx(item_path) == 0 + + item_path = "$.verifiableCredential[42]" + assert _extract_cred_idx(item_path) == 42 + + +def test__get_predicate_type_and_value(): + pred_filter: dict[str, int] = {"exclusiveMinimum": 10} + assert _get_predicate_type_and_value(pred_filter) == (">", 10) + + pred_filter = {"exclusiveMaximum": 20} + assert _get_predicate_type_and_value(pred_filter) == ("<", 20) + + pred_filter = {"minimum": 5} + assert _get_predicate_type_and_value(pred_filter) == (">=", 5) + + pred_filter = {"maximum": 15} + assert _get_predicate_type_and_value(pred_filter) == ("<=", 15) + + +@pytest.mark.asyncio +async def test__load_w3c_credentials(): + credentials = [VC] + + w3c_creds = await _load_w3c_credentials(credentials) + + assert len(w3c_creds) == len(credentials) + + with pytest.raises(LinkedDataProofException) as context: + credentials = [{"schema": "invalid"}] + await _load_w3c_credentials(credentials) + assert "Error loading credential as W3C credential" + + +@pytest.mark.asyncio +async def test_create_rev_states(): + w3c_creds_metadata = [ + {"rev_reg_id": "rev_reg_id_1", "rev_reg_index": 0, "timestamp": 1234567890}, + {"rev_reg_id": "rev_reg_id_2", "rev_reg_index": 1, "timestamp": 1234567890}, + ] + rev_reg_defs = { + "rev_reg_id_1": {"id": "rev_reg_id_1", "definition": "rev_reg_def_1"}, + "rev_reg_id_2": {"id": "rev_reg_id_2", "definition": "rev_reg_def_2"}, + } + rev_reg_entries = { + "rev_reg_id_1": {1234567890: "rev_reg_entry_1"}, + "rev_reg_id_2": {1234567890: "rev_reg_entry_2"}, + } + + with mock.patch.object( + RevocationRegistry, + "from_definition", + return_value=mock.CoroutineMock( + get_or_fetch_local_tails_path=mock.CoroutineMock(return_value="tails_path") + ), + ): + with mock.patch.object( + RevocationStatusList, "load", return_value=mock.MagicMock() + ): + with pytest.raises(AnonCredsHolderError): + await create_rev_states( + w3c_creds_metadata, rev_reg_defs, rev_reg_entries + ) + with mock.patch.object( + CredentialRevocationState, "create", return_value=mock.MagicMock() + ) as mock_create: + + result = await create_rev_states( + w3c_creds_metadata, rev_reg_defs, rev_reg_entries + ) + + assert len(result) == 2 + assert mock_create.call_count == 2 diff --git a/aries_cloudagent/vc/vc_di/verify.py b/aries_cloudagent/vc/vc_di/verify.py new file mode 100644 index 0000000000..610a32ef33 --- /dev/null +++ b/aries_cloudagent/vc/vc_di/verify.py @@ -0,0 +1,63 @@ +"""Verifiable Credential and Presentation verification methods.""" + +from aries_cloudagent.anoncreds.holder import AnonCredsHolderError +from ...core.profile import Profile +from ...anoncreds.verifier import AnonCredsVerifier +from ..ld_proofs import ( + ProofPurpose, +) +from ..vc_ld.validation_result import PresentationVerificationResult +from .prove import ( + prepare_data_for_presentation, + _load_w3c_credentials, +) +from anoncreds import AnoncredsError + + +async def verify_signed_anoncredspresentation( + *, + profile: Profile, + presentation: dict, + purpose: ProofPurpose = None, + challenge: str = None, + domain: str = None, + pres_req: dict = None, +) -> PresentationVerificationResult: + """Verify presentation structure, credentials, proof purpose and signature. + + Args: + profile (Profile): The profile to use for verification + presentation (dict): The presentation to verify + purpose (ProofPurpose, optional): Proof purpose to use. + challenge (str, optional): The challenge to use for authentication. + domain (str, optional): Domain to use for the authentication proof purpose. + pres_req (dict, optional): The presentation request to verify against. + + Returns: + PresentationVerificationResult: The result of the verification. Verified property + indicates whether the verification was successful + + """ + + # TODO: I think we should add some sort of options to authenticate the subject id + # to the presentation verification method controller + anoncreds_verifier = AnonCredsVerifier(profile) + + credentials = presentation["verifiableCredential"] + pres_definition = pres_req["presentation_definition"] + w3c_creds = await _load_w3c_credentials(credentials) + + (anoncreds_pres_req, cred_metadata) = await prepare_data_for_presentation( + presentation=presentation, + w3c_creds=w3c_creds, + pres_definition=pres_definition, + profile=profile, + challenge=challenge, + ) + + try: + return await anoncreds_verifier.verify_presentation_w3c( + anoncreds_pres_req, presentation, cred_metadata + ) + except AnoncredsError as err: + raise AnonCredsHolderError("Error loading master secret") from err diff --git a/aries_cloudagent/vc/vc_ld/models/linked_data_proof.py b/aries_cloudagent/vc/vc_ld/models/linked_data_proof.py index 6787e82be7..c441ff5911 100644 --- a/aries_cloudagent/vc/vc_ld/models/linked_data_proof.py +++ b/aries_cloudagent/vc/vc_ld/models/linked_data_proof.py @@ -92,7 +92,7 @@ class Meta: ) created = fields.Str( - required=True, + required=False, validate=INDY_ISO8601_DATETIME_VALIDATE, metadata={ "description": ( diff --git a/demo/features/0453-issue-credential.feature b/demo/features/0453-issue-credential.feature index a6736295b7..d53ffde527 100644 --- a/demo/features/0453-issue-credential.feature +++ b/demo/features/0453-issue-credential.feature @@ -24,7 +24,7 @@ Feature: RFC 0453 Aries agent issue credential | --public-did --mediation | --mediation | driverslicense | Data_DL_NormalizedValues | | | | --public-did --multitenant | --multitenant --log-file | driverslicense | Data_DL_NormalizedValues | | | - @Release @WalletType_Askar_AnonCreds @BasicTest + @Release @WalletType_Askar_AnonCreds @BasicTest @cred_type_vc_di Examples: | Acme_capabilities | Bob_capabilities | Schema_name | Credential_data | Acme_extra | Bob_extra | | --public-did --wallet-type askar-anoncreds | --wallet-type askar-anoncreds | driverslicense | Data_DL_NormalizedValues | | | @@ -62,7 +62,7 @@ Feature: RFC 0453 Aries agent issue credential When "Acme" offers a credential with data Then "Bob" has the credential issued - @WalletType_Askar_AnonCreds @SwitchCredTypeTest + @WalletType_Askar_AnonCreds @SwitchCredTypeTest @cred_type_vc_di Examples: | Acme_capabilities | Bob_capabilities | Schema_name | Credential_data | Acme_extra | Bob_extra | New_Cred_Type | | --public-did --wallet-type askar-anoncreds | --wallet-type askar-anoncreds | driverslicense | Data_DL_NormalizedValues | | | vc_di | diff --git a/demo/features/0454-present-proof.feature b/demo/features/0454-present-proof.feature index bdbc2155c4..41a9dece60 100644 --- a/demo/features/0454-present-proof.feature +++ b/demo/features/0454-present-proof.feature @@ -27,6 +27,11 @@ Feature: RFC 0454 Aries agent present proof | issuer | Acme_capabilities | Bob_capabilities | Schema_name | Credential_data | Proof_request | | Faber | --public-did --wallet-type askar-anoncreds | --wallet-type askar-anoncreds | driverslicense_v2 | Data_DL_MaxValues | DL_age_over_19_v2 | + @Release @WalletType_Askar_AnonCreds @cred_type_vc_di + Examples: + | issuer | Acme_capabilities | Bob_capabilities | Schema_name | Credential_data | Proof_request | + | Faber | --public-did --wallet-type askar-anoncreds --cred-type vc_di | --wallet-type askar-anoncreds | driverslicense_v2 | Data_DL_MaxValues | DL_age_over_19_v2 | + @Release @WalletType_Askar_AnonCreds Examples: | issuer | Acme_capabilities | Bob_capabilities | Schema_name | Credential_data | Proof_request | @@ -142,6 +147,11 @@ Feature: RFC 0454 Aries agent present proof | issuer | Acme_capabilities | Bob_capabilities | Schema_name | Credential_data | Proof_request | | Faber | --revocation --public-did --wallet-type askar-anoncreds | --wallet-type askar-anoncreds | driverslicense_v2 | Data_DL_MaxValues | DL_age_over_19_v2 | + @Release @WalletType_Askar_AnonCreds @cred_type_vc_di + Examples: + | issuer | Acme_capabilities | Bob_capabilities | Schema_name | Credential_data | Proof_request | + | Faber | --revocation --public-did --wallet-type askar-anoncreds --cred-type vc_di | --wallet-type askar-anoncreds | driverslicense_v2 | Data_DL_MaxValues | DL_age_over_19_v2 | + @T002.1-RFC0454 Scenario Outline: Present Proof where the issuer revokes the credential and the proof fails @@ -198,6 +208,11 @@ Feature: RFC 0454 Aries agent present proof | issuer1 | Acme1_capabilities | issuer2 | Acme2_capabilities | Bob_cap | Schema_name_1 | Credential_data_1 | Schema_name_2 | Credential_data_2 | Proof_request | | Acme1 | --revocation --public-did --wallet-type askar-anoncreds | Acme2 | --public-did --wallet-type askar-anoncreds | --wallet-type askar-anoncreds | driverslicense_v2 | Data_DL_MaxValues | health_id | Data_DL_MaxValues | DL_age_over_19_v2_with_health_id | + @Release @WalletType_Askar_AnonCreds @cred_type_vc_di + Examples: + | issuer1 | Acme1_capabilities | issuer2 | Acme2_capabilities | Bob_cap | Schema_name_1 | Credential_data_1 | Schema_name_2 | Credential_data_2 | Proof_request | + | Acme1 | --revocation --public-did --wallet-type askar-anoncreds --cred-type vc_di | Acme2 | --public-did --wallet-type askar-anoncreds | --wallet-type askar-anoncreds | driverslicense_v2 | Data_DL_MaxValues | health_id | Data_DL_MaxValues | DL_age_over_19_v2_with_health_id | + @T003-RFC0454.1f Scenario Outline: Present Proof for multiple credentials where the one is revocable and one isn't, neither credential is revoked, fails due to requesting request-level revocation Given we have "4" agents @@ -279,6 +294,11 @@ Feature: RFC 0454 Aries agent present proof | issuer1 | Acme1_capabilities | issuer2 | Acme2_capabilities | Bob_cap | Schema_name_1 | Credential_data_1 | Schema_name_2 | Credential_data_2 | Proof_request | | Acme1 | --revocation --public-did --wallet-type askar-anoncreds | Acme2 | --public-did --wallet-type askar-anoncreds | --wallet-type askar-anoncreds | driverslicense_v2 | Data_DL_MaxValues | health_id | Data_DL_MaxValues | DL_age_over_19_v2_with_health_id_no_revoc | + @PR @Release @WalletType_Askar_AnonCreds @cred_type_vc_di + Examples: + | issuer1 | Acme1_capabilities | issuer2 | Acme2_capabilities | Bob_cap | Schema_name_1 | Credential_data_1 | Schema_name_2 | Credential_data_2 | Proof_request | + | Acme1 | --revocation --public-did --wallet-type askar-anoncreds --cred-type vc_di | Acme2 | --public-did --wallet-type askar-anoncreds | --wallet-type askar-anoncreds | driverslicense_v2 | Data_DL_MaxValues | health_id | Data_DL_MaxValues | DL_age_over_19_v2_with_health_id_no_revoc | + @T003-RFC0454.4 Scenario Outline: Present Proof for a credential where multiple credentials are issued and all but one are revoked Given we have "3" agents @@ -304,6 +324,11 @@ Feature: RFC 0454 Aries agent present proof | issuer1 | Acme1_capabilities | Bob_cap | Schema_name_1 | Credential_data_1 | Proof_request | | Acme1 | --revocation --public-did --wallet-type askar-anoncreds | --wallet-type askar-anoncreds | driverslicense_v2 | Data_DL_MaxValues | DL_age_over_19_v2 | + @WalletType_Askar_AnonCreds @cred_type_vc_di + Examples: + | issuer1 | Acme1_capabilities | Bob_cap | Schema_name_1 | Credential_data_1 | Proof_request | + | Acme1 | --revocation --public-did --wallet-type askar-anoncreds --cred-type vc_di | --wallet-type askar-anoncreds | driverslicense_v2 | Data_DL_MaxValues | DL_age_over_19_v2 | + @T003-RFC0454.5 Scenario Outline: Present Proof for a vc_di-issued credential using "legacy" indy proof and the proof validates Given we have "2" agents @@ -318,7 +343,7 @@ Feature: RFC 0454 Aries agent present proof When "Acme" sends a request with explicit revocation status for proof presentation to "Bob" Then "Acme" has the proof verified - @WalletType_Askar_AnonCreds @SwitchCredTypeTest + @WalletType_Askar_AnonCreds @SwitchCredTypeTest @cred_type_vc_di Examples: | Acme_capabilities | Bob_capabilities | Schema_name | Credential_data | Acme_extra | Bob_extra | New_Cred_Type | Proof_request | | --public-did --wallet-type askar-anoncreds --cred-type vc_di --revocation | --wallet-type askar-anoncreds | driverslicense_v2 | Data_DL_MaxValues | | | indy | DL_age_over_19_v2 | @@ -338,7 +363,7 @@ Feature: RFC 0454 Aries agent present proof When "Acme" sends a request for proof presentation to "Bob" Then "Acme" has the proof verification fail - @WalletType_Askar_AnonCreds @SwitchCredTypeTest + @WalletType_Askar_AnonCreds @SwitchCredTypeTest @cred_type_vc_di Examples: | Acme_capabilities | Bob_capabilities | Schema_name | Credential_data | Acme_extra | Bob_extra | New_Cred_Type | Proof_request | | --public-did --wallet-type askar-anoncreds --cred-type vc_di --revocation | --wallet-type askar-anoncreds | driverslicense_v2 | Data_DL_MaxValues | | | indy | DL_age_over_19_v2 | diff --git a/demo/runners/agent_container.py b/demo/runners/agent_container.py index 9598412e00..8ef4feb034 100644 --- a/demo/runners/agent_container.py +++ b/demo/runners/agent_container.py @@ -433,6 +433,8 @@ async def handle_present_proof_v2_0(self, message): pres_ex_id = message["pres_ex_id"] self.log(f"Presentation: state = {state}, pres_ex_id = {pres_ex_id}") + print(f"Presentation: state = {state}, pres_ex_id = {pres_ex_id}") + if state in ["request-received"]: # prover role log_status( @@ -465,6 +467,7 @@ async def handle_present_proof_v2_0(self, message): creds = await self.admin_GET( f"/present-proof-2.0/records/{pres_ex_id}/credentials" ) + # print(">>> creds:", creds) if creds: # select only indy credentials creds = [x for x in creds if "cred_info" in x] @@ -523,6 +526,7 @@ async def handle_present_proof_v2_0(self, message): creds = await self.admin_GET( f"/present-proof-2.0/records/{pres_ex_id}/credentials" ) + if creds and 0 < len(creds): # select only dif credentials creds = [x for x in creds if "issuanceDate" in x] diff --git a/demo/runners/faber.py b/demo/runners/faber.py index 6de8018c80..45b74f280d 100644 --- a/demo/runners/faber.py +++ b/demo/runners/faber.py @@ -350,47 +350,49 @@ def generate_proof_request_web_request( "domain": "4jt78h47fh47", }, "presentation_definition": { - "id": "32f54163-7166-48f1-93d8-ff217bdb0654", - "submission_requirements": [ - { - "name": "Degree Verification", - "rule": "pick", - "min": 1, - "from": "A", - } - ], + "id": "5591656f-5b5d-40f8-ab5c-9041c8e3a6a0", + "name": "Age Verification", + "purpose": "We need to verify your age before entering a bar", "input_descriptors": [ { - "id": "degree_input_1", - "name": "Degree Certificate", - "group": ["A"], + "id": "age-verification", + "name": "A specific type of VC + Issuer", + "purpose": "We want a VC of this type generated by this issuer", "schema": [ { "uri": "https://www.w3.org/2018/credentials#VerifiableCredential" - }, - { - "uri": "https://w3id.org/citizenship#PermanentResidentCard" - }, + } ], "constraints": { + "statuses": { + "active": {"directive": "disallowed"} + }, "limit_disclosure": "required", "fields": [ + { + "path": ["$.issuer"], + "filter": { + "type": "string", + "const": self.did, + }, + }, + {"path": ["$.credentialSubject.name"]}, { "path": [ - "$.credentialSubject.degree.name" - ], - "purpose": "We need to verify that you have the required degree.", - "filter": {"type": "string"}, + "$.credentialSubject.degree" + ] }, { "path": [ - "$.credentialSubject.birthDate" + "$.credentialSubject.birthdate_dateint" ], - "purpose": "To ensure you meet the age requirement.", + "predicate": "preferred", "filter": { - "type": "string", - "pattern": birth_date.strftime( - birth_date_format + "type": "number", + "maximum": int( + birth_date.strftime( + birth_date_format + ) ), }, }, @@ -398,10 +400,26 @@ def generate_proof_request_web_request( }, } ], + "format": { + "di_vc": { + "proof_type": ["DataIntegrityProof"], + "cryptosuite": [ + "anoncreds-2023", + "eddsa-rdfc-2022", + ], + } + }, }, }, }, } + + if revocation: + proof_request_web_request["presentation_request"]["dif"][ + "presentation_definition" + ]["input_descriptors"][0]["constraints"]["statuses"]["active"][ + "directive" + ] = "required" if not connectionless: proof_request_web_request["connection_id"] = self.connection_id return proof_request_web_request