From d2c27d3e9fd58388c072f0d81f30dbc5b2d9b3db Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Thu, 5 Oct 2023 14:50:58 -0400 Subject: [PATCH] feat: integrate vc ldp manager in ICv2, PPv2 Signed-off-by: Daniel Bluhm --- .../v2_0/formats/ld_proof/handler.py | 364 +++--------------- .../protocols/present_proof/dif/pres_exch.py | 52 +-- .../present_proof/dif/tests/test_pres_exch.py | 6 +- .../present_proof/v2_0/formats/dif/handler.py | 83 ++-- aries_cloudagent/vc/vc_ld/manager.py | 22 +- 5 files changed, 111 insertions(+), 416 deletions(-) diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/formats/ld_proof/handler.py b/aries_cloudagent/protocols/issue_credential/v2_0/formats/ld_proof/handler.py index f7e2ff8a43..8ab7128cb9 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/formats/ld_proof/handler.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/formats/ld_proof/handler.py @@ -2,7 +2,7 @@ import logging -from typing import Mapping, Optional +from typing import Mapping from marshmallow import EXCLUDE, INCLUDE from pyld import jsonld @@ -11,30 +11,12 @@ from ......messaging.decorators.attach_decorator import AttachDecorator from ......storage.vc_holder.base import VCHolder from ......storage.vc_holder.vc_record import VCRecord -from ......vc.ld_proofs import ( - AuthenticationProofPurpose, - BbsBlsSignature2020, - CredentialIssuancePurpose, - DocumentLoader, - Ed25519Signature2018, - Ed25519Signature2020, - LinkedDataProof, - ProofPurpose, - WalletKeyPair, -) +from ......vc.ld_proofs import DocumentLoader from ......vc.ld_proofs.check import get_properties_without_context -from ......vc.ld_proofs.constants import ( - SECURITY_CONTEXT_BBS_URL, - SECURITY_CONTEXT_ED25519_2020_URL, -) from ......vc.ld_proofs.error import LinkedDataProofException -from ......vc.vc_ld import LDProof, VerifiableCredential, VerifiableCredentialSchema -from ......vc.vc_ld import issue_vc as issue -from ......vc.vc_ld import verify_credential -from ......wallet.base import BaseWallet, DIDInfo -from ......wallet.default_verification_key_strategy import BaseVerificationKeyStrategy -from ......wallet.error import WalletNotFoundError -from ......wallet.key_type import BLS12381G2, ED25519 +from ......vc.vc_ld import VerifiableCredential, VerifiableCredentialSchema +from ......vc.vc_ld.manager import VcLdpManager, VcLdpManagerError +from ......vc.vc_ld.models.options import LDProofVCOptions from ...message_types import ( ATTACHMENT_FORMAT, CRED_20_ISSUE, @@ -50,43 +32,10 @@ from ...models.cred_ex_record import V20CredExRecord from ...models.detail.ld_proof import V20CredExRecordLDProof from ..handler import CredFormatAttachment, V20CredFormatError, V20CredFormatHandler -from .models.cred_detail_options import LDProofVCDetailOptions from .models.cred_detail import LDProofVCDetail, LDProofVCDetailSchema LOGGER = logging.getLogger(__name__) -SUPPORTED_ISSUANCE_PROOF_PURPOSES = { - CredentialIssuancePurpose.term, - AuthenticationProofPurpose.term, -} -SUPPORTED_ISSUANCE_SUITES = {Ed25519Signature2018, Ed25519Signature2020} -SIGNATURE_SUITE_KEY_TYPE_MAPPING = { - Ed25519Signature2018: ED25519, - Ed25519Signature2020: ED25519, -} - - -# We only want to add bbs suites to supported if the module is installed -if BbsBlsSignature2020.BBS_SUPPORTED: - SUPPORTED_ISSUANCE_SUITES.add(BbsBlsSignature2020) - SIGNATURE_SUITE_KEY_TYPE_MAPPING[BbsBlsSignature2020] = BLS12381G2 - - -PROOF_TYPE_SIGNATURE_SUITE_MAPPING = { - suite.signature_type: suite for suite in SIGNATURE_SUITE_KEY_TYPE_MAPPING -} - - -# key_type -> set of signature types mappings -KEY_TYPE_SIGNATURE_TYPE_MAPPING = { - key_type: { - suite.signature_type - for suite, kt in SIGNATURE_SUITE_KEY_TYPE_MAPPING.items() - if kt == key_type - } - for key_type in SIGNATURE_SUITE_KEY_TYPE_MAPPING.values() -} - class LDProofCredFormatHandler(V20CredFormatHandler): """Linked data proof credential format handler.""" @@ -180,223 +129,20 @@ def get_format_data(self, message_type: str, data: dict) -> CredFormatAttachment ), ) - async def _assert_can_issue_with_id_and_proof_type( - self, issuer_id: str, proof_type: str - ): - """Assert that it is possible to issue using the specified id and proof type. - - Args: - issuer_id (str): The issuer id - proof_type (str): the signature suite proof type - - Raises: - V20CredFormatError: - - If the proof type is not supported - - If the issuer id is not a did - - If the did is not found in th wallet - - If the did does not support to create signatures for the proof type - - """ - try: - # Check if it is a proof type we can issue with - if proof_type not in PROOF_TYPE_SIGNATURE_SUITE_MAPPING.keys(): - raise V20CredFormatError( - f"Unable to sign credential with unsupported proof type {proof_type}." - f" Supported proof types: {PROOF_TYPE_SIGNATURE_SUITE_MAPPING.keys()}" - ) - - if not issuer_id.startswith("did:"): - raise V20CredFormatError( - f"Unable to issue credential with issuer id: {issuer_id}." - " Only issuance with DIDs is supported" - ) - - # Retrieve did from wallet. Will throw if not found - did = await self._did_info_for_did(issuer_id) - - # Raise error if we cannot issue a credential with this proof type - # using this DID from - did_proof_types = KEY_TYPE_SIGNATURE_TYPE_MAPPING[did.key_type] - if proof_type not in did_proof_types: - raise V20CredFormatError( - f"Unable to issue credential with issuer id {issuer_id} and proof " - f"type {proof_type}. DID only supports proof types {did_proof_types}" - ) - - except WalletNotFoundError: - raise V20CredFormatError( - f"Issuer did {issuer_id} not found." - " Unable to issue credential with this DID." - ) - - async def _did_info_for_did(self, did: str) -> DIDInfo: - """Get the did info for specified did. - - If the did starts with did:sov it will remove the prefix for - backwards compatibility with not fully qualified did. - - Args: - did (str): The did to retrieve from the wallet. - - Raises: - WalletNotFoundError: If the did is not found in the wallet. - - Returns: - DIDInfo: did information - - """ - async with self.profile.session() as session: - wallet = session.inject(BaseWallet) - - # If the did starts with did:sov we need to query without - if did.startswith("did:sov:"): - return await wallet.get_local_did(did.replace("did:sov:", "")) - - # All other methods we can just query - return await wallet.get_local_did(did) - - async def _get_suite_for_detail( - self, detail: LDProofVCDetail, verification_method: Optional[str] = None - ) -> LinkedDataProof: - issuer_id = detail.credential.issuer_id - proof_type = detail.options.proof_type - - # Assert we can issue the credential based on issuer + proof_type - await self._assert_can_issue_with_id_and_proof_type(issuer_id, proof_type) - - # Create base proof object with options from detail - proof = LDProof( - created=detail.options.created, - domain=detail.options.domain, - challenge=detail.options.challenge, - ) - - did_info = await self._did_info_for_did(issuer_id) - verkey_id_strategy = self.profile.context.inject(BaseVerificationKeyStrategy) - verification_method = ( - verification_method - or await verkey_id_strategy.get_verification_method_id_for_did( - issuer_id, self.profile, proof_purpose="assertionMethod" - ) - ) - - if verification_method is None: - raise V20CredFormatError( - f"Unable to get retrieve verification method for did {issuer_id}" - ) - - suite = await self._get_suite( - proof_type=proof_type, - verification_method=verification_method, - proof=proof.serialize(), - did_info=did_info, - ) - - return suite - - async def _get_suite( - self, - *, - proof_type: str, - verification_method: str = None, - proof: dict = None, - did_info: DIDInfo = None, - ): - """Get signature suite for issuance of verification.""" - # Get signature class based on proof type - SignatureClass = PROOF_TYPE_SIGNATURE_SUITE_MAPPING[proof_type] - - # Generically create signature class - return SignatureClass( - verification_method=verification_method, - proof=proof, - key_pair=WalletKeyPair( - profile=self.profile, - key_type=SIGNATURE_SUITE_KEY_TYPE_MAPPING[SignatureClass], - public_key_base58=did_info.verkey if did_info else None, - ), - ) - - def _get_proof_purpose( - self, *, proof_purpose: str = None, challenge: str = None, domain: str = None - ) -> ProofPurpose: - """Get the proof purpose for a credential detail. - - Args: - proof_purpose (str): The proof purpose string value - challenge (str, optional): Challenge - domain (str, optional): domain - - Raises: - V20CredFormatError: - - If the proof purpose is not supported. - - [authentication] If challenge is missing. - - Returns: - ProofPurpose: Proof purpose instance that can be used for issuance. - - """ - # Default proof purpose is assertionMethod - proof_purpose = proof_purpose or CredentialIssuancePurpose.term - - if proof_purpose == CredentialIssuancePurpose.term: - return CredentialIssuancePurpose() - elif proof_purpose == AuthenticationProofPurpose.term: - # assert challenge is present for authentication proof purpose - if not challenge: - raise V20CredFormatError( - f"Challenge is required for '{proof_purpose}' proof purpose." - ) - - return AuthenticationProofPurpose(challenge=challenge, domain=domain) - else: - raise V20CredFormatError( - f"Unsupported proof purpose: {proof_purpose}. " - f"Supported proof types are: {SUPPORTED_ISSUANCE_PROOF_PURPOSES}" - ) - - async def _prepare_detail( - self, detail: LDProofVCDetail, holder_did: str = None - ) -> LDProofVCDetail: - # Add BBS context if not present yet - assert detail.options and isinstance(detail.options, LDProofVCDetailOptions) - assert detail.credential and isinstance(detail.credential, VerifiableCredential) - if ( - detail.options.proof_type == BbsBlsSignature2020.signature_type - and SECURITY_CONTEXT_BBS_URL not in detail.credential.context_urls - ): - detail.credential.add_context(SECURITY_CONTEXT_BBS_URL) - # Add ED25519-2020 context if not present yet - elif ( - detail.options.proof_type == Ed25519Signature2020.signature_type - and SECURITY_CONTEXT_ED25519_2020_URL not in detail.credential.context_urls - ): - detail.credential.add_context(SECURITY_CONTEXT_ED25519_2020_URL) - - # Permit late binding of credential subject: - # IFF credential subject doesn't already have an id, add holder_did as - # credentialSubject.id (if provided) - subject = detail.credential.credential_subject - - # TODO if credential subject is a list, we're only binding the first... - # How should this be handled? - if isinstance(subject, list): - subject = subject[0] - - if not subject: - raise V20CredFormatError("Credential subject is required") - - if holder_did and holder_did.startswith("did:key") and "id" not in subject: - subject["id"] = holder_did - - return detail - async def create_proposal( self, cred_ex_record: V20CredExRecord, proposal_data: Mapping ) -> CredFormatAttachment: """Create linked data proof credential proposal.""" + manager = VcLdpManager(self.profile) detail = LDProofVCDetail.deserialize(proposal_data) - detail = await self._prepare_detail(detail) + assert detail.options and isinstance(detail.options, LDProofVCOptions) + assert detail.credential and isinstance(detail.credential, VerifiableCredential) + try: + detail.credential = await manager.prepare_credential( + detail.credential, detail.options + ) + except VcLdpManagerError as err: + raise V20CredFormatError("Failed to prepare credential") from err return self.get_format_data(CRED_20_PROPOSAL, detail.serialize()) @@ -419,7 +165,15 @@ async def create_offer( # but also when we create an offer (manager does some weird stuff) offer_data = cred_proposal_message.attachment(LDProofCredFormatHandler.format) detail = LDProofVCDetail.deserialize(offer_data) - detail = await self._prepare_detail(detail) + manager = VcLdpManager(self.profile) + assert detail.options and isinstance(detail.options, LDProofVCOptions) + assert detail.credential and isinstance(detail.credential, VerifiableCredential) + try: + detail.credential = await manager.prepare_credential( + detail.credential, detail.options + ) + except VcLdpManagerError as err: + raise V20CredFormatError("Failed to prepare credential") from err document_loader = self.profile.inject(DocumentLoader) missing_properties = get_properties_without_context( @@ -433,9 +187,14 @@ async def create_offer( ) # Make sure we can issue with the did and proof type - await self._assert_can_issue_with_id_and_proof_type( - detail.credential.issuer_id, detail.options.proof_type - ) + try: + await manager.assert_can_issue_with_id_and_proof_type( + detail.credential.issuer_id, detail.options.proof_type + ) + except VcLdpManagerError as err: + raise V20CredFormatError( + "Checking whether issuance is possible failed" + ) from err return self.get_format_data(CRED_20_OFFER, detail.serialize()) @@ -466,7 +225,15 @@ async def create_request( ) detail = LDProofVCDetail.deserialize(request_data) - detail = await self._prepare_detail(detail, holder_did=holder_did) + manager = VcLdpManager(self.profile) + assert detail.options and isinstance(detail.options, LDProofVCOptions) + assert detail.credential and isinstance(detail.credential, VerifiableCredential) + try: + detail.credential = await manager.prepare_credential( + detail.credential, detail.options, holder_did=holder_did + ) + except VcLdpManagerError as err: + raise V20CredFormatError("Failed to prepare credential") from err return self.get_format_data(CRED_20_REQUEST, detail.serialize()) @@ -518,28 +285,15 @@ async def issue_credential( LDProofCredFormatHandler.format ) detail = LDProofVCDetail.deserialize(detail_dict) - detail = await self._prepare_detail(detail) - - # Get signature suite, proof purpose and document loader - suite = await self._get_suite_for_detail( - detail, cred_ex_record.verification_method - ) - proof_purpose = self._get_proof_purpose( - proof_purpose=detail.options.proof_purpose, - challenge=detail.options.challenge, - domain=detail.options.domain, - ) - document_loader = self.profile.inject(DocumentLoader) - - # issue the credential - vc = await issue( - credential=detail.credential.serialize(), - suite=suite, - document_loader=document_loader, - purpose=proof_purpose, - ) + manager = VcLdpManager(self.profile) + assert detail.options and isinstance(detail.options, LDProofVCOptions) + assert detail.credential and isinstance(detail.credential, VerifiableCredential) + try: + vc = await manager.issue(detail.credential, detail.options) + except VcLdpManagerError as err: + raise V20CredFormatError("Failed to issue credential") from err - return self.get_format_data(CRED_20_ISSUE, vc) + return self.get_format_data(CRED_20_ISSUE, vc.serialize()) async def receive_credential( self, cred_ex_record: V20CredExRecord, cred_issue_message: V20CredIssue @@ -622,27 +376,17 @@ async def store_credential( credential = VerifiableCredential.deserialize(cred_dict, unknown=INCLUDE) # Get signature suite, proof purpose and document loader - suite = await self._get_suite(proof_type=credential.proof.type) - - purpose = self._get_proof_purpose( - proof_purpose=credential.proof.proof_purpose, - challenge=credential.proof.challenge, - domain=credential.proof.domain, - ) - document_loader = self.profile.inject(DocumentLoader) - - # Verify the credential - result = await verify_credential( - credential=cred_dict, - suites=[suite], - document_loader=document_loader, - purpose=purpose, - ) + manager = VcLdpManager(self.profile) + try: + result = await manager.verify_credential(credential) + except VcLdpManagerError as err: + raise V20CredFormatError("Failed to verify credential") from err if not result.verified: raise V20CredFormatError(f"Received invalid credential: {result}") # Saving expanded type as a cred_tag + document_loader = self.profile.inject(DocumentLoader) expanded = jsonld.expand(cred_dict, options={"documentLoader": document_loader}) types = JsonLdProcessor.get_values( expanded[0], diff --git a/aries_cloudagent/protocols/present_proof/dif/pres_exch.py b/aries_cloudagent/protocols/present_proof/dif/pres_exch.py index c7f974ddb1..7018da2d78 100644 --- a/aries_cloudagent/protocols/present_proof/dif/pres_exch.py +++ b/aries_cloudagent/protocols/present_proof/dif/pres_exch.py @@ -1,5 +1,5 @@ """Schemas for dif presentation exchange attachment.""" -from typing import Mapping, Sequence, Union +from typing import Mapping, Optional, Sequence from marshmallow import ( EXCLUDE, @@ -12,13 +12,11 @@ ) from ....messaging.models.base import BaseModel, BaseModelSchema -from ....messaging.valid import ( - UUID4_EXAMPLE, - UUID4_VALIDATE, - StrOrDictField, - StrOrNumberField, +from ....messaging.valid import StrOrNumberField, UUID4_EXAMPLE, UUID4_VALIDATE +from ....vc.vc_ld.models.presentation import ( + VerifiablePresentation, + VerifiablePresentationSchema, ) -from ....vc.vc_ld import LinkedDataProofSchema class ClaimFormat(BaseModel): @@ -840,60 +838,34 @@ class Meta: ) -class VerifiablePresentation(BaseModel): +class VPWithSubmission(VerifiablePresentation): """Single VerifiablePresentation object.""" class Meta: """VerifiablePresentation metadata.""" - schema_class = "VerifiablePresentationSchema" + schema_class = "VPWithSubmissionSchema" def __init__( self, *, - id: str = None, - contexts: Sequence[Union[str, dict]] = None, - types: Sequence[str] = None, - credentials: Sequence[dict] = None, - proof: Sequence[dict] = None, - presentation_submission: PresentationSubmission = None, + presentation_submission: Optional[PresentationSubmission] = None, + **kwargs, ): """Initialize VerifiablePresentation.""" - self.id = id - self.contexts = contexts - self.types = types - self.credentials = credentials - self.proof = proof + super().__init__(**kwargs) self.presentation_submission = presentation_submission -class VerifiablePresentationSchema(BaseModelSchema): +class VPWithSubmissionSchema(VerifiablePresentationSchema): """Single Verifiable Presentation Schema.""" class Meta: """VerifiablePresentationSchema metadata.""" - model_class = VerifiablePresentation + model_class = VPWithSubmission unknown = INCLUDE - id = fields.Str( - required=False, - validate=UUID4_VALIDATE, - metadata={"description": "ID", "example": UUID4_EXAMPLE}, - ) - contexts = fields.List(StrOrDictField(), data_key="@context") - types = fields.List( - fields.Str(required=False, metadata={"description": "Types"}), data_key="type" - ) - credentials = fields.List( - fields.Dict(required=False, metadata={"description": "Credentials"}), - data_key="verifiableCredential", - ) - proof = fields.Nested( - LinkedDataProofSchema(), - required=True, - metadata={"description": "The proof of the credential"}, - ) presentation_submission = fields.Nested(PresentationSubmissionSchema) diff --git a/aries_cloudagent/protocols/present_proof/dif/tests/test_pres_exch.py b/aries_cloudagent/protocols/present_proof/dif/tests/test_pres_exch.py index b8515188cd..6544e88d88 100644 --- a/aries_cloudagent/protocols/present_proof/dif/tests/test_pres_exch.py +++ b/aries_cloudagent/protocols/present_proof/dif/tests/test_pres_exch.py @@ -11,7 +11,7 @@ DIFHolder, Filter, Constraints, - VerifiablePresentation, + VPWithSubmission, SchemasInputDescriptorFilter, ) @@ -377,8 +377,8 @@ def test_verifiable_presentation_wrapper(self): "jws": "eyJhbGciOiAiRWREU0EiLCAiYjY0IjogZmFsc2UsICJjcml0IjogWyJiNjQiXX0..2uBYmg7muE9ZPVeAGo_ibVfLkCjf2hGshr2o5i8pAwFyNBM-kDHXofuq1MzJgb19wzb01VIu91hY_ajjt9KFAA", }, } - vp = VerifiablePresentation.deserialize(test_vp_dict) - assert isinstance(vp, VerifiablePresentation) + vp = VPWithSubmission.deserialize(test_vp_dict) + assert isinstance(vp, VPWithSubmission) def test_schemas_input_desc_filter(self): test_schema_list = [ 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 8a656368f8..42b0451762 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 @@ -2,10 +2,10 @@ import json import logging +from typing import Mapping, Sequence, Tuple +from uuid import uuid4 from marshmallow import RAISE -from typing import Mapping, Tuple, Sequence -from uuid import uuid4 from ......messaging.base_handler import BaseResponder from ......messaging.decorators.attach_decorator import AttachDecorator @@ -13,38 +13,29 @@ from ......storage.vc_holder.base import VCHolder from ......storage.vc_holder.vc_record import VCRecord from ......vc.ld_proofs import ( - DocumentLoader, + BbsBlsSignature2020, Ed25519Signature2018, Ed25519Signature2020, - BbsBlsSignature2020, - BbsBlsSignatureProof2020, - WalletKeyPair, ) -from ......vc.vc_ld.verify import verify_presentation -from ......wallet.key_type import ED25519, BLS12381G2 - +from ......vc.vc_ld.manager import VcLdpManager +from ......vc.vc_ld.models.options import LDProofVCOptions +from ......vc.vc_ld.models.presentation import VerifiablePresentation from .....problem_report.v1_0.message import ProblemReport - from ....dif.pres_exch import PresentationDefinition, SchemaInputDescriptor -from ....dif.pres_exch_handler import DIFPresExchHandler, DIFPresExchError +from ....dif.pres_exch_handler import DIFPresExchError, DIFPresExchHandler from ....dif.pres_proposal_schema import DIFProofProposalSchema -from ....dif.pres_request_schema import ( - DIFProofRequestSchema, - DIFPresSpecSchema, -) +from ....dif.pres_request_schema import DIFPresSpecSchema, DIFProofRequestSchema from ....dif.pres_schema import DIFProofSchema from ....v2_0.messages.pres_problem_report import ProblemReportReason - from ...message_types import ( ATTACHMENT_FORMAT, - PRES_20_REQUEST, PRES_20, PRES_20_PROPOSAL, + PRES_20_REQUEST, ) -from ...messages.pres_format import V20PresFormat from ...messages.pres import V20Pres +from ...messages.pres_format import V20PresFormat from ...models.pres_exchange import V20PresExRecord - from ..handler import V20PresFormatHandler, V20PresFormatHandlerError LOGGER = logging.getLogger(__name__) @@ -55,26 +46,6 @@ class DIFPresFormatHandler(V20PresFormatHandler): format = V20PresFormat.Format.DIF - ISSUE_SIGNATURE_SUITE_KEY_TYPE_MAPPING = { - Ed25519Signature2018: ED25519, - Ed25519Signature2020: ED25519, - } - - if BbsBlsSignature2020.BBS_SUPPORTED: - ISSUE_SIGNATURE_SUITE_KEY_TYPE_MAPPING[BbsBlsSignature2020] = BLS12381G2 - ISSUE_SIGNATURE_SUITE_KEY_TYPE_MAPPING[BbsBlsSignatureProof2020] = BLS12381G2 - - async def _get_all_suites(self): - """Get all supported suites for verifying presentation.""" - suites = [] - for suite, key_type in self.ISSUE_SIGNATURE_SUITE_KEY_TYPE_MAPPING.items(): - suites.append( - suite( - key_pair=WalletKeyPair(profile=self._profile, key_type=key_type), - ) - ) - return suites - @classmethod def validate_fields(cls, message_type: str, attachment_data: Mapping): """Validate attachment data for a specific message type. @@ -474,27 +445,31 @@ async def verify_pres(self, pres_ex_record: V20PresExRecord) -> V20PresExRecord: pres_request = pres_ex_record.pres_request.attachment( DIFPresFormatHandler.format ) - challenge = None - if "options" in pres_request: - challenge = pres_request["options"].get("challenge", str(uuid4())) - if not challenge: - challenge = str(uuid4()) + 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: + raise V20PresFormatHandlerError( + "Presentation exchange record has no presentations to verify" + ) for proof in dif_proof: - pres_ver_result = await verify_presentation( - presentation=proof, - suites=await self._get_all_suites(), - document_loader=self._profile.inject(DocumentLoader), - challenge=challenge, + pres_ver_result = await manager.verify_presentation( + vp=VerifiablePresentation.deserialize(proof), + options=options, ) if not pres_ver_result.verified: break else: - pres_ver_result = await verify_presentation( - presentation=dif_proof, - suites=await self._get_all_suites(), - document_loader=self._profile.inject(DocumentLoader), - challenge=challenge, + pres_ver_result = await manager.verify_presentation( + vp=VerifiablePresentation.deserialize(dif_proof), + options=options, ) + + assert pres_ver_result is not None pres_ex_record.verified = json.dumps(pres_ver_result.verified) return pres_ex_record diff --git a/aries_cloudagent/vc/vc_ld/manager.py b/aries_cloudagent/vc/vc_ld/manager.py index 0e7a8a2a42..27b7fbf39f 100644 --- a/aries_cloudagent/vc/vc_ld/manager.py +++ b/aries_cloudagent/vc/vc_ld/manager.py @@ -9,6 +9,7 @@ ) from ...core.profile import Profile +from ..vc_ld.models.presentation import VerifiablePresentation from ...wallet.base import BaseWallet from ...wallet.default_verification_key_strategy import BaseVerificationKeyStrategy from ...wallet.did_info import DIDInfo @@ -102,8 +103,8 @@ async def _did_info_for_did(self, did: str) -> DIDInfo: # All other methods we can just query return await wallet.get_local_did(did) - async def _assert_can_issue_with_id_and_proof_type( - self, issuer_id: str, proof_type: str + async def assert_can_issue_with_id_and_proof_type( + self, issuer_id: Optional[str], proof_type: Optional[str] ): """Assert that it is possible to issue using the specified id and proof type. @@ -119,6 +120,11 @@ async def _assert_can_issue_with_id_and_proof_type( - If the did does not support to create signatures for the proof type """ + if not issuer_id or not proof_type: + raise VcLdpManagerError( + "Issuer id and proof type are required to issue a credential." + ) + try: # Check if it is a proof type we can issue with if proof_type not in PROOF_TYPE_SIGNATURE_SUITE_MAPPING.keys(): @@ -216,15 +222,14 @@ def _get_proof_purpose( f"Supported proof types are: {SUPPORTED_ISSUANCE_PROOF_PURPOSES}" ) - async def _prepare_credential( + async def prepare_credential( self, credential: VerifiableCredential, options: LDProofVCOptions, holder_did: Optional[str] = None, ) -> VerifiableCredential: + """Prepare a credential for issuance.""" # Add BBS context if not present yet - assert options and isinstance(options, LDProofVCOptions) - assert credential and isinstance(credential, VerifiableCredential) if ( options.proof_type == BbsBlsSignature2020.signature_type and SECURITY_CONTEXT_BBS_URL not in credential.context_urls @@ -268,7 +273,7 @@ async def _get_suite_for_credential( raise VcLdpManagerError("Proof type is required") # Assert we can issue the credential based on issuer + proof_type - await self._assert_can_issue_with_id_and_proof_type(issuer_id, proof_type) + await self.assert_can_issue_with_id_and_proof_type(issuer_id, proof_type) # Create base proof object with options proof = LDProof( @@ -315,7 +320,7 @@ async def issue( self, credential: VerifiableCredential, options: LDProofVCOptions ) -> VerifiableCredential: """Sign a VC with a Linked Data Proof.""" - credential = await self._prepare_credential(credential, options) + credential = await self.prepare_credential(credential, options) # Get signature suite, proof purpose and document loader suite = await self._get_suite_for_credential(credential, options) @@ -326,7 +331,6 @@ async def issue( ) document_loader = self.profile.inject(DocumentLoader) - # issue the credential vc = await ldp_issue( credential=credential.serialize(), suite=suite, @@ -336,7 +340,7 @@ async def issue( return VerifiableCredential.deserialize(vc) async def verify_presentation( - self, vp: VerifiableCredential, options: LDProofVCOptions + self, vp: VerifiablePresentation, options: LDProofVCOptions ) -> PresentationVerificationResult: """Verify a VP with a Linked Data Proof.""" if not options.challenge: