Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add vcdm 2.0 model and context #3436

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

PatStLouis
Copy link
Contributor

Needs issuanceDate validation for vcdm 1.0 (add a current timestamp if absent)
Needs hooking up with didcomm

Signed-off-by: PatStLouis <patrick.st-louis@opsecid.ca>
@PatStLouis PatStLouis requested review from dbluhm and jamshale January 10, 2025 19:43
… date with vcdm 1.1

Signed-off-by: PatStLouis <patrick.st-louis@opsecid.ca>
Signed-off-by: PatStLouis <patrick.st-louis@opsecid.ca>
Signed-off-by: PatStLouis <patrick.st-louis@opsecid.ca>
Copy link
Contributor

@ff137 ff137 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just some early review comments

@@ -253,13 +289,16 @@ def proof(self, proof: LDProof):
def __eq__(self, o: object) -> bool:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can maybe be made more legible with:

    def __eq__(self, o: object) -> bool:
        """Check equality."""
        if not isinstance(o, VerifiableCredential):
            return False

        return all(
            getattr(self, attr) == getattr(o, attr)
            for attr in vars(self)
        )

or, if vars(self) returns extra properties that should be ignored, then define the relevant attributes in a list:

    def __eq__(self, o: object) -> bool:
        """Check equality."""
        if not isinstance(o, VerifiableCredential):
            return False

        attributes = [
            "context", "id", "type", "issuer", "issuance_date",
            "expiration_date", "valid_from", "valid_until",
            "credential_subject", "credential_status", "proof", "extra"
        ]

        return all(
            getattr(self, attr) == getattr(o, attr)
            for attr in attributes
        )

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll look into it

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's probably more than one place that this pattern is used and can be improved. So, could perhaps change all at once in a separate PR. But I haven't checked where else it's done.
I'm just a fan of making things more readable / maintainable :-)

Comment on lines 889 to 897
"""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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If value is None, there would be a TypeError.

Probably worth asserting that value is of expected type before calling len or [0].
(because a dev could try call with a string, and get unclear error message).

Something like:

    def __call__(self, value: List[str]) -> List[str]:
        """Validate input value."""
        if not isinstance(value, list):
            raise ValidationError("Value must be a non-empty list.")

        if not value or value[0] not in CredentialContext.FIRST_CONTEXT:
            raise ValidationError(
                f"First context must be one of {CredentialContext.FIRST_CONTEXT}"
            )

        return value

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can implement this, however this type of validation would of been done at the datamodel version in the class invoking this validation

@@ -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 = [
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps rename to a VALID_CONTEXTS?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah I agree

@@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps better to have options.proof_type != Ed25519Signature2020.signature_type

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I disagree, maybe I should change the comment. We want to explicitly prevent the 2018 version, other types might be fine down the line. I mentioned 2020 because this is the only other current registered type, but the BLS2020 stuff would be okay as well.

The limitation of 2018 is because it was implicitly included in the vcdm 1.1 context, while 2020 has its own unique context url.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense. Soon after I commented, I thought that the best would be for there to be a list of supported signature types, and then the condition would just assert the requested type is in the list. That would allow the error message to indicate "please use one of the supported types: ", and can easily be expanded in the future

Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
62.9% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants