From 7ba9b99677f00be60ab3b2ed91781c917b9f0167 Mon Sep 17 00:00:00 2001 From: PatStLouis Date: Fri, 10 Jan 2025 19:41:28 +0000 Subject: [PATCH 1/4] add vcdm 2.0 model and context Signed-off-by: PatStLouis --- acapy_agent/messaging/valid.py | 11 +- acapy_agent/vc/ld_proofs/constants.py | 1 + .../vc/ld_proofs/document_downloader.py | 1 + .../resources/credentials_v2_context.jsonld | 301 ++++++++++++++++++ acapy_agent/vc/vc_ld/models/credential.py | 61 +++- 5 files changed, 369 insertions(+), 6 deletions(-) create mode 100644 acapy_agent/vc/ld_proofs/resources/credentials_v2_context.jsonld diff --git a/acapy_agent/messaging/valid.py b/acapy_agent/messaging/valid.py index c5026ca382..276e2aaac6 100644 --- a/acapy_agent/messaging/valid.py +++ b/acapy_agent/messaging/valid.py @@ -875,8 +875,11 @@ def __call__(self, value): class CredentialContext(Validator): """Credential Context.""" - FIRST_CONTEXT = "https://www.w3.org/2018/credentials/v1" - EXAMPLE = [FIRST_CONTEXT, "https://www.w3.org/2018/credentials/examples/v1"] + FIRST_CONTEXT = [ + "https://www.w3.org/2018/credentials/v1", + "https://www.w3.org/ns/credentials/v2", + ] + EXAMPLE = [FIRST_CONTEXT[0], "https://www.w3.org/2018/credentials/examples/v1"] def __init__(self) -> None: """Initialize the instance.""" @@ -886,9 +889,9 @@ def __call__(self, value): """Validate input value.""" length = len(value) - if length < 1 or value[0] != CredentialContext.FIRST_CONTEXT: + if length < 1 or value[0] not in CredentialContext.FIRST_CONTEXT: raise ValidationError( - f"First context must be {CredentialContext.FIRST_CONTEXT}" + f"First context must be one of {CredentialContext.FIRST_CONTEXT}" ) return value diff --git a/acapy_agent/vc/ld_proofs/constants.py b/acapy_agent/vc/ld_proofs/constants.py index 2a4780f484..e84fbc5d62 100644 --- a/acapy_agent/vc/ld_proofs/constants.py +++ b/acapy_agent/vc/ld_proofs/constants.py @@ -6,6 +6,7 @@ SECURITY_CONTEXT_URL = SECURITY_CONTEXT_V2_URL DID_V1_CONTEXT_URL = "https://www.w3.org/ns/did/v1" CREDENTIALS_CONTEXT_V1_URL = "https://www.w3.org/2018/credentials/v1" +CREDENTIALS_CONTEXT_V2_URL = "https://www.w3.org/ns/credentials/v2" SECURITY_CONTEXT_BBS_URL = "https://w3id.org/security/bbs/v1" SECURITY_CONTEXT_ED25519_2020_URL = "https://w3id.org/security/suites/ed25519-2020/v1" diff --git a/acapy_agent/vc/ld_proofs/document_downloader.py b/acapy_agent/vc/ld_proofs/document_downloader.py index 553e00dd39..7f7ffbeec5 100644 --- a/acapy_agent/vc/ld_proofs/document_downloader.py +++ b/acapy_agent/vc/ld_proofs/document_downloader.py @@ -40,6 +40,7 @@ class StaticCacheJsonLdDownloader: CONTEXT_FILE_MAPPING = { "https://www.w3.org/2018/credentials/v1": "credentials_context.jsonld", + "https://www.w3.org/ns/credentials/v2": "credentials_v2_context.jsonld", "https://w3id.org/vc/status-list/2021/v1": "status_list_context.jsonld", "https://www.w3.org/ns/did/v1": "did_documents_context.jsonld", "https://w3id.org/security/v1": "security-v1-context.jsonld", diff --git a/acapy_agent/vc/ld_proofs/resources/credentials_v2_context.jsonld b/acapy_agent/vc/ld_proofs/resources/credentials_v2_context.jsonld new file mode 100644 index 0000000000..bb4a78b630 --- /dev/null +++ b/acapy_agent/vc/ld_proofs/resources/credentials_v2_context.jsonld @@ -0,0 +1,301 @@ +{ + "@context": { + "@protected": true, + "id": "@id", + "type": "@type", + "description": "https://schema.org/description", + "digestMultibase": { + "@id": "https://w3id.org/security#digestMultibase", + "@type": "https://w3id.org/security#multibase" + }, + "digestSRI": { + "@id": "https://www.w3.org/2018/credentials#digestSRI", + "@type": "https://www.w3.org/2018/credentials#sriString" + }, + "mediaType": { + "@id": "https://schema.org/encodingFormat" + }, + "name": "https://schema.org/name", + "VerifiableCredential": { + "@id": "https://www.w3.org/2018/credentials#VerifiableCredential", + "@context": { + "@protected": true, + "id": "@id", + "type": "@type", + "confidenceMethod": { + "@id": "https://www.w3.org/2018/credentials#confidenceMethod", + "@type": "@id" + }, + "credentialSchema": { + "@id": "https://www.w3.org/2018/credentials#credentialSchema", + "@type": "@id" + }, + "credentialStatus": { + "@id": "https://www.w3.org/2018/credentials#credentialStatus", + "@type": "@id" + }, + "credentialSubject": { + "@id": "https://www.w3.org/2018/credentials#credentialSubject", + "@type": "@id" + }, + "description": "https://schema.org/description", + "evidence": { + "@id": "https://www.w3.org/2018/credentials#evidence", + "@type": "@id" + }, + "issuer": { + "@id": "https://www.w3.org/2018/credentials#issuer", + "@type": "@id" + }, + "name": "https://schema.org/name", + "proof": { + "@id": "https://w3id.org/security#proof", + "@type": "@id", + "@container": "@graph" + }, + "refreshService": { + "@id": "https://www.w3.org/2018/credentials#refreshService", + "@type": "@id" + }, + "relatedResource": { + "@id": "https://www.w3.org/2018/credentials#relatedResource", + "@type": "@id" + }, + "renderMethod": { + "@id": "https://www.w3.org/2018/credentials#renderMethod", + "@type": "@id" + }, + "termsOfUse": { + "@id": "https://www.w3.org/2018/credentials#termsOfUse", + "@type": "@id" + }, + "validFrom": { + "@id": "https://www.w3.org/2018/credentials#validFrom", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, + "validUntil": { + "@id": "https://www.w3.org/2018/credentials#validUntil", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + } + } + }, + "EnvelopedVerifiableCredential": "https://www.w3.org/2018/credentials#EnvelopedVerifiableCredential", + "VerifiablePresentation": { + "@id": "https://www.w3.org/2018/credentials#VerifiablePresentation", + "@context": { + "@protected": true, + "id": "@id", + "type": "@type", + "holder": { + "@id": "https://www.w3.org/2018/credentials#holder", + "@type": "@id" + }, + "proof": { + "@id": "https://w3id.org/security#proof", + "@type": "@id", + "@container": "@graph" + }, + "termsOfUse": { + "@id": "https://www.w3.org/2018/credentials#termsOfUse", + "@type": "@id" + }, + "verifiableCredential": { + "@id": "https://www.w3.org/2018/credentials#verifiableCredential", + "@type": "@id", + "@container": "@graph", + "@context": null + } + } + }, + "EnvelopedVerifiablePresentation": "https://www.w3.org/2018/credentials#EnvelopedVerifiablePresentation", + "JsonSchemaCredential": "https://www.w3.org/2018/credentials#JsonSchemaCredential", + "JsonSchema": { + "@id": "https://www.w3.org/2018/credentials#JsonSchema", + "@context": { + "@protected": true, + "id": "@id", + "type": "@type", + "jsonSchema": { + "@id": "https://www.w3.org/2018/credentials#jsonSchema", + "@type": "@json" + } + } + }, + "BitstringStatusListCredential": "https://www.w3.org/ns/credentials/status#BitstringStatusListCredential", + "BitstringStatusList": { + "@id": "https://www.w3.org/ns/credentials/status#BitstringStatusList", + "@context": { + "@protected": true, + "id": "@id", + "type": "@type", + "encodedList": { + "@id": "https://www.w3.org/ns/credentials/status#encodedList", + "@type": "https://w3id.org/security#multibase" + }, + "statusMessage": { + "@id": "https://www.w3.org/ns/credentials/status#statusMessage", + "@context": { + "@protected": true, + "id": "@id", + "type": "@type", + "message": "https://www.w3.org/ns/credentials/status#message", + "status": "https://www.w3.org/ns/credentials/status#status" + } + }, + "statusPurpose": "https://www.w3.org/ns/credentials/status#statusPurpose", + "statusReference": { + "@id": "https://www.w3.org/ns/credentials/status#statusReference", + "@type": "@id" + }, + "statusSize": { + "@id": "https://www.w3.org/ns/credentials/status#statusSize", + "@type": "https://www.w3.org/2001/XMLSchema#positiveInteger" + }, + "ttl": "https://www.w3.org/ns/credentials/status#ttl" + } + }, + "BitstringStatusListEntry": { + "@id": "https://www.w3.org/ns/credentials/status#BitstringStatusListEntry", + "@context": { + "@protected": true, + "id": "@id", + "type": "@type", + "statusListCredential": { + "@id": "https://www.w3.org/ns/credentials/status#statusListCredential", + "@type": "@id" + }, + "statusListIndex": "https://www.w3.org/ns/credentials/status#statusListIndex", + "statusPurpose": "https://www.w3.org/ns/credentials/status#statusPurpose" + } + }, + "DataIntegrityProof": { + "@id": "https://w3id.org/security#DataIntegrityProof", + "@context": { + "@protected": true, + "id": "@id", + "type": "@type", + "challenge": "https://w3id.org/security#challenge", + "created": { + "@id": "http://purl.org/dc/terms/created", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, + "cryptosuite": { + "@id": "https://w3id.org/security#cryptosuite", + "@type": "https://w3id.org/security#cryptosuiteString" + }, + "domain": "https://w3id.org/security#domain", + "expires": { + "@id": "https://w3id.org/security#expiration", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, + "nonce": "https://w3id.org/security#nonce", + "previousProof": { + "@id": "https://w3id.org/security#previousProof", + "@type": "@id" + }, + "proofPurpose": { + "@id": "https://w3id.org/security#proofPurpose", + "@type": "@vocab", + "@context": { + "@protected": true, + "id": "@id", + "type": "@type", + "assertionMethod": { + "@id": "https://w3id.org/security#assertionMethod", + "@type": "@id", + "@container": "@set" + }, + "authentication": { + "@id": "https://w3id.org/security#authenticationMethod", + "@type": "@id", + "@container": "@set" + }, + "capabilityDelegation": { + "@id": "https://w3id.org/security#capabilityDelegationMethod", + "@type": "@id", + "@container": "@set" + }, + "capabilityInvocation": { + "@id": "https://w3id.org/security#capabilityInvocationMethod", + "@type": "@id", + "@container": "@set" + }, + "keyAgreement": { + "@id": "https://w3id.org/security#keyAgreementMethod", + "@type": "@id", + "@container": "@set" + } + } + }, + "proofValue": { + "@id": "https://w3id.org/security#proofValue", + "@type": "https://w3id.org/security#multibase" + }, + "verificationMethod": { + "@id": "https://w3id.org/security#verificationMethod", + "@type": "@id" + } + } + }, + "...": { + "@id": "https://www.iana.org/assignments/jwt#..." + }, + "_sd": { + "@id": "https://www.iana.org/assignments/jwt#_sd", + "@type": "@json" + }, + "_sd_alg": { + "@id": "https://www.iana.org/assignments/jwt#_sd_alg" + }, + "aud": { + "@id": "https://www.iana.org/assignments/jwt#aud", + "@type": "@id" + }, + "cnf": { + "@id": "https://www.iana.org/assignments/jwt#cnf", + "@context": { + "@protected": true, + "kid": { + "@id": "https://www.iana.org/assignments/jwt#kid", + "@type": "@id" + }, + "jwk": { + "@id": "https://www.iana.org/assignments/jwt#jwk", + "@type": "@json" + } + } + }, + "exp": { + "@id": "https://www.iana.org/assignments/jwt#exp", + "@type": "https://www.w3.org/2001/XMLSchema#nonNegativeInteger" + }, + "iat": { + "@id": "https://www.iana.org/assignments/jwt#iat", + "@type": "https://www.w3.org/2001/XMLSchema#nonNegativeInteger" + }, + "iss": { + "@id": "https://www.iana.org/assignments/jose#iss", + "@type": "@id" + }, + "jku": { + "@id": "https://www.iana.org/assignments/jose#jku", + "@type": "@id" + }, + "kid": { + "@id": "https://www.iana.org/assignments/jose#kid", + "@type": "@id" + }, + "nbf": { + "@id": "https://www.iana.org/assignments/jwt#nbf", + "@type": "https://www.w3.org/2001/XMLSchema#nonNegativeInteger" + }, + "sub": { + "@id": "https://www.iana.org/assignments/jose#sub", + "@type": "@id" + }, + "x5u": { + "@id": "https://www.iana.org/assignments/jose#x5u", + "@type": "@id" + } + } +} \ No newline at end of file diff --git a/acapy_agent/vc/vc_ld/models/credential.py b/acapy_agent/vc/vc_ld/models/credential.py index 8ba27a9a4b..540e3c593d 100644 --- a/acapy_agent/vc/vc_ld/models/credential.py +++ b/acapy_agent/vc/vc_ld/models/credential.py @@ -26,6 +26,7 @@ ) from ...ld_proofs.constants import ( CREDENTIALS_CONTEXT_V1_URL, + CREDENTIALS_CONTEXT_V2_URL, VERIFIABLE_CREDENTIAL_TYPE, ) from .linked_data_proof import LDProof, LinkedDataProofSchema @@ -47,6 +48,8 @@ def __init__( issuer: Optional[Union[dict, str]] = None, issuance_date: Optional[str] = None, expiration_date: Optional[str] = None, + valid_from: Optional[str] = None, + valid_until: Optional[str] = None, credential_subject: Optional[Union[dict, List[dict]]] = None, credential_status: Optional[Union[dict, List[dict]]] = None, proof: Optional[Union[dict, LDProof]] = None, @@ -63,6 +66,8 @@ def __init__( # TODO: proper date parsing self._issuance_date = issuance_date self._expiration_date = expiration_date + self._valid_from = valid_from + self._valid_until = valid_until self._proof = proof @@ -79,7 +84,7 @@ def context(self, context: List[Union[str, dict]]): First item must be credentials v1 url """ - assert context[0] == CREDENTIALS_CONTEXT_V1_URL + assert context[0] in [CREDENTIALS_CONTEXT_V1_URL, CREDENTIALS_CONTEXT_V2_URL] self._context = context @@ -195,6 +200,36 @@ def expiration_date(self, date: Union[str, datetime, None]): self._expiration_date = date + @property + def valid_from(self): + """Getter for valid from date.""" + return self._valid_from + + @valid_from.setter + def valid_from(self, date: Union[str, datetime]): + """Setter for valid from date.""" + if isinstance(date, datetime): + if not date.tzinfo: + date = date.replace(tzinfo=tz.UTC) + date = date.isoformat() + + self._valid_from = date + + @property + def valid_until(self): + """Getter for valid until date.""" + return self._valid_until + + @valid_until.setter + def valid_until(self, date: Union[str, datetime, None]): + """Setter for valid until date.""" + if isinstance(date, datetime): + if not date.tzinfo: + date = date.replace(tzinfo=tz.UTC) + date = date.isoformat() + + self._valid_until = date + @property def credential_subject_ids(self) -> List[str]: """Getter for credential subject ids.""" @@ -260,6 +295,8 @@ def __eq__(self, o: object) -> bool: and self.issuer == o.issuer and self.issuance_date == o.issuance_date and self.expiration_date == o.expiration_date + and self.valid_from == o.valid_from + and self.valid_until == o.valid_until and self.credential_subject == o.credential_subject and self.credential_status == o.credential_status and self.proof == o.proof @@ -325,7 +362,7 @@ class Meta: issuance_date = fields.Str( data_key="issuanceDate", - required=True, + required=False, validate=RFC3339_DATETIME_VALIDATE, metadata={ "description": "The issuance date", @@ -343,6 +380,26 @@ class Meta: }, ) + valid_from = fields.Str( + data_key="validFrom", + required=False, + validate=RFC3339_DATETIME_VALIDATE, + metadata={ + "description": "The valid from date", + "example": RFC3339_DATETIME_EXAMPLE, + }, + ) + + valid_until = fields.Str( + data_key="validUntil", + required=False, + validate=RFC3339_DATETIME_VALIDATE, + metadata={ + "description": "The valid until date", + "example": RFC3339_DATETIME_EXAMPLE, + }, + ) + credential_subject = DictOrDictListField( required=True, data_key="credentialSubject", From 9879a4a30e0b42e685b34b74629e99a577dc3dd3 Mon Sep 17 00:00:00 2001 From: PatStLouis Date: Fri, 10 Jan 2025 20:28:35 +0000 Subject: [PATCH 2/4] add backwards compatibility checks: limit proof type and add issuance date with vcdm 1.1 Signed-off-by: PatStLouis --- acapy_agent/vc/vc_ld/manager.py | 19 ++++++++++++++++++- acapy_agent/vc/vc_ld/models/credential.py | 2 ++ acapy_agent/vc/vc_ld/models/presentation.py | 3 ++- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/acapy_agent/vc/vc_ld/manager.py b/acapy_agent/vc/vc_ld/manager.py index bf3fa83bc4..82fae4f65c 100644 --- a/acapy_agent/vc/vc_ld/manager.py +++ b/acapy_agent/vc/vc_ld/manager.py @@ -1,5 +1,6 @@ """Manager for performing Linked Data Proof signatures over JSON-LD formatted W3C VCs.""" +from datetime import datetime, timezone from typing import Dict, List, Optional, Type, Union, cast from pyld import jsonld @@ -14,6 +15,8 @@ from ...wallet.error import WalletNotFoundError from ...wallet.key_type import BLS12381G2, ED25519, KeyType from ..ld_proofs.constants import ( + CREDENTIALS_CONTEXT_V1_URL, + CREDENTIALS_CONTEXT_V2_URL, SECURITY_CONTEXT_BBS_URL, SECURITY_CONTEXT_ED25519_2020_URL, ) @@ -271,6 +274,12 @@ async def prepare_credential( and SECURITY_CONTEXT_ED25519_2020_URL not in credential.context_urls ): credential.add_context(SECURITY_CONTEXT_ED25519_2020_URL) + # Limit VCDM 2.0 with Ed25519Signature2020 + elif ( + options.proof_type == Ed25519Signature2018.signature_type + and credential.context_urls[0] == CREDENTIALS_CONTEXT_V2_URL + ): + raise VcLdpManagerError("Invalid proof type, use Ed25519Signature2020.") # Permit late binding of credential subject: # IFF credential subject doesn't already have an id, add holder_did as @@ -281,7 +290,15 @@ async def prepare_credential( # How should this be handled? if isinstance(subject, list): subject = subject[0] - + + if ( + not credential.issuance_date + and credential.context_urls[0] == CREDENTIALS_CONTEXT_V1_URL + ): + credential.issuance_date = str( + datetime.now(timezone.utc).isoformat('T', 'seconds') + ) + if not subject: raise VcLdpManagerError("Credential subject is required") diff --git a/acapy_agent/vc/vc_ld/models/credential.py b/acapy_agent/vc/vc_ld/models/credential.py index 540e3c593d..01a3db2b1a 100644 --- a/acapy_agent/vc/vc_ld/models/credential.py +++ b/acapy_agent/vc/vc_ld/models/credential.py @@ -182,6 +182,7 @@ def issuance_date(self, date: Union[str, datetime]): if not date.tzinfo: date = date.replace(tzinfo=tz.UTC) date = date.isoformat() + self._issuance_date = date @@ -288,6 +289,7 @@ def proof(self, proof: LDProof): def __eq__(self, o: object) -> bool: """Check equality.""" if isinstance(o, VerifiableCredential): + return ( self.context == o.context and self.id == o.id diff --git a/acapy_agent/vc/vc_ld/models/presentation.py b/acapy_agent/vc/vc_ld/models/presentation.py index 06d437f743..e288a77e84 100644 --- a/acapy_agent/vc/vc_ld/models/presentation.py +++ b/acapy_agent/vc/vc_ld/models/presentation.py @@ -16,6 +16,7 @@ ) from ...ld_proofs.constants import ( CREDENTIALS_CONTEXT_V1_URL, + CREDENTIALS_CONTEXT_V2_URL, VERIFIABLE_PRESENTATION_TYPE, ) from .linked_data_proof import LDProof, LinkedDataProofSchema @@ -61,7 +62,7 @@ def context(self, context: List[Union[str, dict]]): First item must be credentials v1 url """ - assert context[0] == CREDENTIALS_CONTEXT_V1_URL + assert context[0] in [CREDENTIALS_CONTEXT_V1_URL, CREDENTIALS_CONTEXT_V2_URL] self._context = context From 262ae0aae08ce7326fd9996aeaed045a6d15dd1c Mon Sep 17 00:00:00 2001 From: PatStLouis Date: Fri, 10 Jan 2025 20:43:19 +0000 Subject: [PATCH 3/4] seperate test fixtures, lint Signed-off-by: PatStLouis --- .../v2_0/formats/ld_proof/tests/fixtures.py | 68 +++++++++++++++++ .../formats/ld_proof/tests/test_handler.py | 75 ++----------------- acapy_agent/vc/vc_ld/manager.py | 8 +- acapy_agent/vc/vc_ld/models/credential.py | 2 - 4 files changed, 78 insertions(+), 75 deletions(-) create mode 100644 acapy_agent/protocols/issue_credential/v2_0/formats/ld_proof/tests/fixtures.py diff --git a/acapy_agent/protocols/issue_credential/v2_0/formats/ld_proof/tests/fixtures.py b/acapy_agent/protocols/issue_credential/v2_0/formats/ld_proof/tests/fixtures.py new file mode 100644 index 0000000000..02c6205092 --- /dev/null +++ b/acapy_agent/protocols/issue_credential/v2_0/formats/ld_proof/tests/fixtures.py @@ -0,0 +1,68 @@ +TEST_DID_SOV = "did:sov:LjgpST2rjsoxYegQDRm7EL" +TEST_DID_KEY = "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL" + +LD_PROOF_VC_DETAIL = { + "credential": { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://www.w3.org/2018/credentials/examples/v1", + ], + "type": ["VerifiableCredential", "UniversityDegreeCredential"], + "credentialSubject": {"test": "key"}, + "issuanceDate": "2021-04-12", + "issuer": TEST_DID_KEY, + }, + "options": { + "proofType": "Ed25519Signature2018", + "created": "2019-12-11T03:50:55", + }, +} +LD_PROOF_VC_DETAIL_BBS = { + "credential": { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://www.w3.org/2018/credentials/examples/v1", + ], + "type": ["VerifiableCredential", "UniversityDegreeCredential"], + "credentialSubject": {"test": "key"}, + "issuanceDate": "2021-04-12", + "issuer": TEST_DID_KEY, + }, + "options": { + "proofType": "BbsBlsSignature2020", + "created": "2019-12-11T03:50:55", + }, +} +LD_PROOF_VC_DETAIL_ED25519_2020 = { + "credential": { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://www.w3.org/2018/credentials/examples/v1", + ], + "type": ["VerifiableCredential", "UniversityDegreeCredential"], + "credentialSubject": {"test": "key"}, + "issuanceDate": "2021-04-12", + "issuer": TEST_DID_KEY, + }, + "options": { + "proofType": "Ed25519Signature2020", + "created": "2019-12-11T03:50:55", + }, +} +LD_PROOF_VC = { + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://www.w3.org/2018/credentials/examples/v1", + ], + "type": ["VerifiableCredential", "UniversityDegreeCredential"], + "credentialSubject": {"test": "key"}, + "issuanceDate": "2021-04-12", + "issuer": TEST_DID_KEY, + "proof": { + "proofPurpose": "assertionMethod", + "created": "2019-12-11T03:50:55", + "type": "Ed25519Signature2018", + "verificationMethod": "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL", + "jws": "eyJhbGciOiAiRWREU0EiLCAiYjY0IjogZmFsc2UsICJjcml0IjogWyJiNjQiXX0..Q6amIrxGiSbM7Ce6DxlfwLCjVcYyclas8fMxaecspXFUcFW9DAAxKzgHx93FWktnlZjM_biitkMgZdStgvivAQ", + }, +} diff --git a/acapy_agent/protocols/issue_credential/v2_0/formats/ld_proof/tests/test_handler.py b/acapy_agent/protocols/issue_credential/v2_0/formats/ld_proof/tests/test_handler.py index 33a92abbc9..c9e39e0fcc 100644 --- a/acapy_agent/protocols/issue_credential/v2_0/formats/ld_proof/tests/test_handler.py +++ b/acapy_agent/protocols/issue_credential/v2_0/formats/ld_proof/tests/test_handler.py @@ -43,75 +43,12 @@ from ..handler import LOGGER as LD_PROOF_LOGGER from ..handler import LDProofCredFormatHandler from ..models.cred_detail import LDProofVCDetail - -TEST_DID_SOV = "did:sov:LjgpST2rjsoxYegQDRm7EL" -TEST_DID_KEY = "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL" - -LD_PROOF_VC_DETAIL = { - "credential": { - "@context": [ - "https://www.w3.org/2018/credentials/v1", - "https://www.w3.org/2018/credentials/examples/v1", - ], - "type": ["VerifiableCredential", "UniversityDegreeCredential"], - "credentialSubject": {"test": "key"}, - "issuanceDate": "2021-04-12", - "issuer": TEST_DID_KEY, - }, - "options": { - "proofType": "Ed25519Signature2018", - "created": "2019-12-11T03:50:55", - }, -} -LD_PROOF_VC_DETAIL_BBS = { - "credential": { - "@context": [ - "https://www.w3.org/2018/credentials/v1", - "https://www.w3.org/2018/credentials/examples/v1", - ], - "type": ["VerifiableCredential", "UniversityDegreeCredential"], - "credentialSubject": {"test": "key"}, - "issuanceDate": "2021-04-12", - "issuer": TEST_DID_KEY, - }, - "options": { - "proofType": "BbsBlsSignature2020", - "created": "2019-12-11T03:50:55", - }, -} -LD_PROOF_VC_DETAIL_ED25519_2020 = { - "credential": { - "@context": [ - "https://www.w3.org/2018/credentials/v1", - "https://www.w3.org/2018/credentials/examples/v1", - ], - "type": ["VerifiableCredential", "UniversityDegreeCredential"], - "credentialSubject": {"test": "key"}, - "issuanceDate": "2021-04-12", - "issuer": TEST_DID_KEY, - }, - "options": { - "proofType": "Ed25519Signature2020", - "created": "2019-12-11T03:50:55", - }, -} -LD_PROOF_VC = { - "@context": [ - "https://www.w3.org/2018/credentials/v1", - "https://www.w3.org/2018/credentials/examples/v1", - ], - "type": ["VerifiableCredential", "UniversityDegreeCredential"], - "credentialSubject": {"test": "key"}, - "issuanceDate": "2021-04-12", - "issuer": TEST_DID_KEY, - "proof": { - "proofPurpose": "assertionMethod", - "created": "2019-12-11T03:50:55", - "type": "Ed25519Signature2018", - "verificationMethod": "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL", - "jws": "eyJhbGciOiAiRWREU0EiLCAiYjY0IjogZmFsc2UsICJjcml0IjogWyJiNjQiXX0..Q6amIrxGiSbM7Ce6DxlfwLCjVcYyclas8fMxaecspXFUcFW9DAAxKzgHx93FWktnlZjM_biitkMgZdStgvivAQ", - }, -} +from .fixtures import ( + LD_PROOF_VC_DETAIL, + LD_PROOF_VC_DETAIL_BBS, + LD_PROOF_VC_DETAIL_ED25519_2020, + LD_PROOF_VC, +) class TestV20LDProofCredFormatHandler(IsolatedAsyncioTestCase): diff --git a/acapy_agent/vc/vc_ld/manager.py b/acapy_agent/vc/vc_ld/manager.py index 82fae4f65c..dc6aabd619 100644 --- a/acapy_agent/vc/vc_ld/manager.py +++ b/acapy_agent/vc/vc_ld/manager.py @@ -290,15 +290,15 @@ async def prepare_credential( # How should this be handled? if isinstance(subject, list): subject = subject[0] - + if ( - not credential.issuance_date + not credential.issuance_date and credential.context_urls[0] == CREDENTIALS_CONTEXT_V1_URL ): credential.issuance_date = str( - datetime.now(timezone.utc).isoformat('T', 'seconds') + datetime.now(timezone.utc).isoformat("T", "seconds") ) - + if not subject: raise VcLdpManagerError("Credential subject is required") diff --git a/acapy_agent/vc/vc_ld/models/credential.py b/acapy_agent/vc/vc_ld/models/credential.py index 01a3db2b1a..540e3c593d 100644 --- a/acapy_agent/vc/vc_ld/models/credential.py +++ b/acapy_agent/vc/vc_ld/models/credential.py @@ -182,7 +182,6 @@ def issuance_date(self, date: Union[str, datetime]): if not date.tzinfo: date = date.replace(tzinfo=tz.UTC) date = date.isoformat() - self._issuance_date = date @@ -289,7 +288,6 @@ def proof(self, proof: LDProof): def __eq__(self, o: object) -> bool: """Check equality.""" if isinstance(o, VerifiableCredential): - return ( self.context == o.context and self.id == o.id From 595168388bd4dc12c88a35ba89f5b2d150970018 Mon Sep 17 00:00:00 2001 From: PatStLouis Date: Fri, 10 Jan 2025 20:54:46 +0000 Subject: [PATCH 4/4] replace issuanceDate tests Signed-off-by: PatStLouis --- .../v2_0/formats/ld_proof/tests/test_handler.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/acapy_agent/protocols/issue_credential/v2_0/formats/ld_proof/tests/test_handler.py b/acapy_agent/protocols/issue_credential/v2_0/formats/ld_proof/tests/test_handler.py index c9e39e0fcc..b72e1175bd 100644 --- a/acapy_agent/protocols/issue_credential/v2_0/formats/ld_proof/tests/test_handler.py +++ b/acapy_agent/protocols/issue_credential/v2_0/formats/ld_proof/tests/test_handler.py @@ -103,7 +103,7 @@ async def test_validate_fields(self): incorrect_detail = { **LD_PROOF_VC_DETAIL, - "credential": {**LD_PROOF_VC_DETAIL["credential"], "issuanceDate": None}, + "credential": {**LD_PROOF_VC_DETAIL["credential"], "credentialSubject": None}, } # test incorrect proposal @@ -121,8 +121,7 @@ async def test_validate_fields(self): # test incorrect cred with self.assertRaises(ValidationError): incorrect_cred = LD_PROOF_VC.copy() - incorrect_cred.pop("issuanceDate") - + incorrect_cred.pop("credentialSubject") self.handler.validate_fields(CRED_20_ISSUE, incorrect_cred) async def test_get_ld_proof_detail_record(self):