diff --git a/sdjwt/pex.py b/sdjwt/pex.py index 01f6d3d..a20183d 100644 --- a/sdjwt/pex.py +++ b/sdjwt/pex.py @@ -1,9 +1,11 @@ from jsonschema import exceptions, validate, ValidationError import json +import uuid import base64 from typing import List, Dict, Union, Optional, Tuple, Any from pydantic import BaseModel from sdjwt.didkey import DIDKey +from sdjwt.sdjwt import get_all_disclosures_with_sd_from_token, decode_disclosure_base64 from jwcrypto import jwk, jwt from jsonpath_ng import jsonpath, parse from dataclasses import dataclass @@ -487,3 +489,158 @@ def update_disclosures_in_token(token: str, disclosures: list) -> str: sd_jwt = jwt_token + sd_string return sd_jwt + + +def decode_credential_sd_to_credential_subject_with_key_mapping( + disclosure_mapping: dict, credential_subject: dict +) -> dict: + credential_subject = {"credentialSubject": credential_subject} + _credentialSubject = {**credential_subject} + key_mapping = {} + + def replace_sd_with_credential_subject_attributes(sds: list, disclosure: dict): + credential_attribute = {} + for sd in sds: + disclosure_base64 = disclosure.get(sd) + key, value = decode_disclosure_base64(disclosure_base64=disclosure_base64) + id = str(uuid.uuid4()) + key_mapping[id] = value + credential_attribute[key] = id + return credential_attribute, key_mapping + + def update_value(obj, path, credential_attribute): + # Construct json path dot notation + dot_notation_path = ".".join(path) + + # Find matches for the json path + jp = parse(dot_notation_path) + matches = jp.find(obj) + + # Iterate through the matches + for match in matches: + if isinstance(match.context.value, dict): + match.context.value[str(match.path)].pop("_sd") + for key, value in credential_attribute.items(): + match.context.value[str(match.path)][key] = value + + def iterate_mapping(obj, path): + for key, value in obj.items(): + + if isinstance(value, dict): + new_path = path + [f"'{key}'"] + # Check if sd is present or not + if "_sd" in value and value["_sd"]: + credential_attribute, key_mapping = ( + replace_sd_with_credential_subject_attributes( + value["_sd"], disclosure=disclosure_mapping + ) + ) + update_value(_credentialSubject, new_path, credential_attribute) + iterate_mapping(value, new_path) + + iterate_mapping(credential_subject, []) + return credential_subject["credentialSubject"], key_mapping + + +def match_credentials_for_sd_jwt( + input_descriptor_json, + credentials, +) -> Tuple[List[MatchedCredential], Optional[Exception]]: + # Deserialise input descriptor json string + try: + descriptor = json.loads(input_descriptor_json) + except json.JSONDecodeError as e: + return [], e + + # To store the matched credentials + matches = [] + + # Iterate through each credential + for item in credentials: + for credential_id, credential_token in item.items(): + + # Assume credential matches until proven otherwise + credential_matched = True + matched_fields = [] + disclosure_mapping = get_all_disclosures_with_sd_from_token( + token=credential_token + ) + _, credential_decoded = decode_header_and_claims_in_jwt(credential_token) + credential_subject, key_mapping = ( + decode_credential_sd_to_credential_subject_with_key_mapping( + disclosure_mapping=disclosure_mapping, + credential_subject=credential_decoded.get("vc").get( + "credentialSubject" + ), + ) + ) + credential = credential_decoded.get("vc") + credential["credentialSubject"] = credential_subject + credential = json.dumps(credential) + + # Iterate through fields specified in the constraints + for field_index, field in enumerate(descriptor["constraints"]["fields"]): + + # Assume field matches until proven otherwise + field_matched = False + + # Iterate through JSON paths for the current field + for path_index, path in enumerate(field["path"]): + + # Apply JSON path on the credential + path_matches, err = apply_json_path(credential, path) + + if len(path_matches) > 0 and err is None: + if "filter" in field: + try: + filter_bytes = json.dumps(field["filter"]) + except (TypeError, ValueError) as e: + # Continue to next path, since filter has failed to serialise + continue + + # Validate the matched JSON against the field's filter + if ( + validate_json_schema(path_matches[0], filter_bytes) + is not None + ): + # Field doesn't match since validation failed + field_matched = False + break + + # Add the matched field to the list + field_matched = True + value = key_mapping.get(str(path_matches[0])) + if value: + + matched_fields.append( + MatchedField( + index=field_index, + path=MatchedPath( + path=path, index=path_index, value=value + ), + ) + ) + else: + matched_fields.append( + MatchedField( + index=field_index, + path=MatchedPath( + path=path, + index=path_index, + value=path_matches[0], + ), + ) + ) + + if not field_matched: + # If any one field didn't match then move to next credential + credential_matched = False + break + + if credential_matched: + # All fields matched, then credential is matched + matches.append( + MatchedCredential(index=credential_id, fields=matched_fields) + ) + + return matches, None diff --git a/sdjwt/tests/test_pex.py b/sdjwt/tests/test_pex.py index 3d91228..ad5ab1a 100644 --- a/sdjwt/tests/test_pex.py +++ b/sdjwt/tests/test_pex.py @@ -7,6 +7,7 @@ MatchedField, MatchedPath, extract_disclosure_values, + match_credentials_for_sd_jwt, ) @@ -159,7 +160,6 @@ async def test_extract_disclosure_values(self): "validFrom": "2024-06-14T09:58:04Z", } - disclosure = { "7sCYwjBINYYha3SbjxvLpdt8q-uUjcxA0HC5z2N15Vs": "WyI1YjU2ZGU2ZjUzZWFmYjQ2NzAzYmRjMTU3NmE2MzE2ZDZlZmI5Mjk0ZDgyNzUyODI4NTI5ZDIzNTJhY2ZkNWZlIiwibGVnYWxTdGF0dXMiLCJ0ZXN0Il0", "WgasKnzLW0ZxJ4tUg_INr0Qs51DLqda_A_JXadqM1Iw": "WyI1NDNkMWNiMmE5MGNkM2ExMmVjNmFlZDRmZTI1YjljY2RiNmU3MmI5N2I1YTZkNTI4OWYzM2ZlYTZiMzA2YWU0IiwiYWRtaW5Vbml0TGV2ZWwxIiwidHkiXQ", @@ -173,7 +173,11 @@ async def test_extract_disclosure_values(self): "limit_disclosure": "required", "fields": [ {"path": ["$.credentialSubject.registeredAddress.adminUnitLevel1"]}, - {"path": ["$.credentialSubject.registeredAddress.locatorDesignator"]}, + { + "path": [ + "$.credentialSubject.registeredAddress.locatorDesignator" + ] + }, {"path": ["$.credentialSubject.legalStatus"]}, { "path": ["$.credentialSubject.legalForm"], @@ -182,9 +186,17 @@ async def test_extract_disclosure_values(self): ], }, } - expected_disclosures = ['WyI1NDNkMWNiMmE5MGNkM2ExMmVjNmFlZDRmZTI1YjljY2RiNmU3MmI5N2I1YTZkNTI4OWYzM2ZlYTZiMzA2YWU0IiwiYWRtaW5Vbml0TGV2ZWwxIiwidHkiXQ', 'WyIwMThlN2VkYmRlNzkyMzhiNTAzNDBiMzNhNDM3ODRmYWFhYWViNTdlNWM4YzZlZjY5MTlmODM2MGZkMzdjMDRhIiwibG9jYXRvckRlc2lnbmF0b3IiLCJ0dTkiXQ', 'WyI1YjU2ZGU2ZjUzZWFmYjQ2NzAzYmRjMTU3NmE2MzE2ZDZlZmI5Mjk0ZDgyNzUyODI4NTI5ZDIzNTJhY2ZkNWZlIiwibGVnYWxTdGF0dXMiLCJ0ZXN0Il0'] + expected_disclosures = [ + "WyI1NDNkMWNiMmE5MGNkM2ExMmVjNmFlZDRmZTI1YjljY2RiNmU3MmI5N2I1YTZkNTI4OWYzM2ZlYTZiMzA2YWU0IiwiYWRtaW5Vbml0TGV2ZWwxIiwidHkiXQ", + "WyIwMThlN2VkYmRlNzkyMzhiNTAzNDBiMzNhNDM3ODRmYWFhYWViNTdlNWM4YzZlZjY5MTlmODM2MGZkMzdjMDRhIiwibG9jYXRvckRlc2lnbmF0b3IiLCJ0dTkiXQ", + "WyI1YjU2ZGU2ZjUzZWFmYjQ2NzAzYmRjMTU3NmE2MzE2ZDZlZmI5Mjk0ZDgyNzUyODI4NTI5ZDIzNTJhY2ZkNWZlIiwibGVnYWxTdGF0dXMiLCJ0ZXN0Il0", + ] - disclosures = extract_disclosure_values(input_descriptor=input_descriptor,credential=credential,disclosure=disclosure) + disclosures = extract_disclosure_values( + input_descriptor=input_descriptor, + credential=credential, + disclosure=disclosure, + ) condition_1 = disclosures == expected_disclosures self.assert_( @@ -198,6 +210,130 @@ async def test_extract_disclosure_values(self): "Expected disclosure doesn't match with result", ) + async def test_match_credentials_for_sd_jwt(self): + credentials = [ + { + "73c30c00-8188-481d-b983-dd3528081c18": "eyJhbGciOiJFUzI1NiIsImtpZCI6ImRpZDprZXk6ejJkbXpEODFjZ1B4OFZraTdKYnV1TW1GWXJXUGdZb3l0eWtVWjNleXFodDFqOUticHBZRnp1NnlRNWZ0cExibnV4RFNON3RaS29wdTNwWXJzem94RXdRaHFkOUhWdmdlOUpGamU1YjlNd3pVb1g2MVByQ01nc1hkQ2p0TEFzTTdWQk5kNmk1M3ZTQ2g5b3gyN0RMRUd3Mm5YTTJGaVZSTmRuaTVxSGlHZTR5RDVDZEJyayN6MmRtekQ4MWNnUHg4VmtpN0pidXVNbUZZcldQZ1lveXR5a1VaM2V5cWh0MWo5S2JwcFlGenU2eVE1ZnRwTGJudXhEU043dFpLb3B1M3BZcnN6b3hFd1FocWQ5SFZ2Z2U5SkZqZTViOU13elVvWDYxUHJDTWdzWGRDanRMQXNNN1ZCTmQ2aTUzdlNDaDlveDI3RExFR3cyblhNMkZpVlJOZG5pNXFIaUdlNHlENUNkQnJrIiwidHlwIjoiSldUIn0.eyJleHAiOjE3MjM5MjcxODAsImlhdCI6MTcyMTMzNTE4MCwiaXNzIjoiZGlkOmtleTp6MmRtekQ4MWNnUHg4VmtpN0pidXVNbUZZcldQZ1lveXR5a1VaM2V5cWh0MWo5S2JwcFlGenU2eVE1ZnRwTGJudXhEU043dFpLb3B1M3BZcnN6b3hFd1FocWQ5SFZ2Z2U5SkZqZTViOU13elVvWDYxUHJDTWdzWGRDanRMQXNNN1ZCTmQ2aTUzdlNDaDlveDI3RExFR3cyblhNMkZpVlJOZG5pNXFIaUdlNHlENUNkQnJrIiwianRpIjoidXJuOmRpZDpiNGNhYmFkMC1hMDg3LTQyZGEtODU0Yi00ZWE4MThlM2ZjMjgiLCJuYmYiOjE3MjEzMzUxODAsInN1YiI6ImRpZDprZXk6ejJkbXpEODFjZ1B4OFZraTdKYnV1TW1GWXJXUGdZb3l0eWtVWjNleXFodDFqOUticThlY05BQ3RpbWRKakFtYVQ3TDJWR3JKZWlXRXVGeDFkU2ZuUFZTdlBVQUpuaG96bmY0cEp0WHA5czdvWTZMaEJMaHNEQ0VQTkdjTW5pU0F4ODJ3SjNrODNpejduTUFteHRYbXFOWlJTTWRaeWlMQTI4NFZORkJrMzQ3c1BTeUxGTiIsInZjIjp7IkBjb250ZXh0IjpbImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL3YxIl0sImNyZWRlbnRpYWxTY2hlbWEiOlt7ImlkIjoiaHR0cHM6Ly9hcGktY29uZm9ybWFuY2UuZWJzaS5ldS90cnVzdGVkLXNjaGVtYXMtcmVnaXN0cnkvdjIvc2NoZW1hcy96M01nVUZVa2I3MjJ1cTR4M2R2NXlBSm1uTm16REZlSzVVQzh4ODNRb2VMSk0iLCJ0eXBlIjoiRnVsbEpzb25TY2hlbWFWYWxpZGF0b3IyMDIxIn1dLCJjcmVkZW50aWFsU3ViamVjdCI6eyJfc2QiOlsicExXTmczQ2o3aHVHbmZsNllHdjd3dVZUWThETzg3OGNqWGxYMXFJcFM4YyIsIlhPcGViRUJFbTdvMHloSUNKc0dWRG5IUUl3Y2JZbFpCVjR0YWpYdGpkSWsiXSwiaWQiOiJkaWQ6a2V5OnoyZG16RDgxY2dQeDhWa2k3SmJ1dU1tRllyV1BnWW95dHlrVVozZXlxaHQxajlLYnE4ZWNOQUN0aW1kSmpBbWFUN0wyVkdySmVpV0V1RngxZFNmblBWU3ZQVUFKbmhvem5mNHBKdFhwOXM3b1k2TGhCTGhzRENFUE5HY01uaVNBeDgyd0ozazgzaXo3bk1BbXh0WG1xTlpSU01kWnlpTEEyODRWTkZCazM0N3NQU3lMRk4ifSwiZXhwaXJhdGlvbkRhdGUiOiIyMDI0LTA4LTE3VDIwOjM5OjQwWiIsImlkIjoidXJuOmRpZDpiNGNhYmFkMC1hMDg3LTQyZGEtODU0Yi00ZWE4MThlM2ZjMjgiLCJpc3N1YW5jZURhdGUiOiIyMDI0LTA3LTE4VDIwOjM5OjQwWiIsImlzc3VlZCI6IjIwMjQtMDctMThUMjA6Mzk6NDBaIiwiaXNzdWVyIjoiZGlkOmtleTp6MmRtekQ4MWNnUHg4VmtpN0pidXVNbUZZcldQZ1lveXR5a1VaM2V5cWh0MWo5S2JwcFlGenU2eVE1ZnRwTGJudXhEU043dFpLb3B1M3BZcnN6b3hFd1FocWQ5SFZ2Z2U5SkZqZTViOU13elVvWDYxUHJDTWdzWGRDanRMQXNNN1ZCTmQ2aTUzdlNDaDlveDI3RExFR3cyblhNMkZpVlJOZG5pNXFIaUdlNHlENUNkQnJrIiwidHlwZSI6WyJWZXJpZmlhYmxlTGVnYWxQZXJzb25hbERhdGEyIl0sInZhbGlkRnJvbSI6IjIwMjQtMDctMThUMjA6Mzk6NDBaIn19.SZiOPw6V8uWHbIlwXVo6bYNNxVQEiU7diprOHQlmHkEIcIF5v2jPCmYfqWK5nQxkN1CxV81OMmUtX4tc-jVDgw~WyJlMzRhOGE2OGU3Y2RiMGFhZWVkOGJkY2MxOTQ5NmI2MTgzZWYzMmRlMTI3ZDVlODZiODdkZDY5ZTdhNjJmYzNjIiwiaWRlbnRpZmllciIsIjAwMCJd~WyJiODkzZGFiNTAxZTAxMDdhM2Y5NTI5ZDg1MTNhODdiZjQwNWI0MTQ4YTEyZjViMDczMDExZDEzOTBhNGM4ZDlhIiwibGVnYWxOYW1lIiwiYWxiaW4iXQ" + }, + { + "915cf847-f870-41cf-a4e8-60282db6cde7": "eyJhbGciOiJFUzI1NiIsImtpZCI6ImRpZDprZXk6ejJkbXpEODFjZ1B4OFZraTdKYnV1TW1GWXJXUGdZb3l0eWtVWjNleXFodDFqOUticHBZRnp1NnlRNWZ0cExibnV4RFNON3RaS29wdTNwWXJzem94RXdRaHFkOUhWdmdlOUpGamU1YjlNd3pVb1g2MVByQ01nc1hkQ2p0TEFzTTdWQk5kNmk1M3ZTQ2g5b3gyN0RMRUd3Mm5YTTJGaVZSTmRuaTVxSGlHZTR5RDVDZEJyayN6MmRtekQ4MWNnUHg4VmtpN0pidXVNbUZZcldQZ1lveXR5a1VaM2V5cWh0MWo5S2JwcFlGenU2eVE1ZnRwTGJudXhEU043dFpLb3B1M3BZcnN6b3hFd1FocWQ5SFZ2Z2U5SkZqZTViOU13elVvWDYxUHJDTWdzWGRDanRMQXNNN1ZCTmQ2aTUzdlNDaDlveDI3RExFR3cyblhNMkZpVlJOZG5pNXFIaUdlNHlENUNkQnJrIiwidHlwIjoiSldUIn0.eyJleHAiOjE3MjM5MjczNTMsImlhdCI6MTcyMTMzNTM1MywiaXNzIjoiZGlkOmtleTp6MmRtekQ4MWNnUHg4VmtpN0pidXVNbUZZcldQZ1lveXR5a1VaM2V5cWh0MWo5S2JwcFlGenU2eVE1ZnRwTGJudXhEU043dFpLb3B1M3BZcnN6b3hFd1FocWQ5SFZ2Z2U5SkZqZTViOU13elVvWDYxUHJDTWdzWGRDanRMQXNNN1ZCTmQ2aTUzdlNDaDlveDI3RExFR3cyblhNMkZpVlJOZG5pNXFIaUdlNHlENUNkQnJrIiwianRpIjoidXJuOmRpZDoxMGRmYTM4MC0wODFlLTQxMDMtYWQ0Ni1hODU4NzUwZTJiOWQiLCJuYmYiOjE3MjEzMzUzNTMsInN1YiI6ImRpZDprZXk6ejJkbXpEODFjZ1B4OFZraTdKYnV1TW1GWXJXUGdZb3l0eWtVWjNleXFodDFqOUticThlY05BQ3RpbWRKakFtYVQ3TDJWR3JKZWlXRXVGeDFkU2ZuUFZTdlBVQUpuaG96bmY0cEp0WHA5czdvWTZMaEJMaHNEQ0VQTkdjTW5pU0F4ODJ3SjNrODNpejduTUFteHRYbXFOWlJTTWRaeWlMQTI4NFZORkJrMzQ3c1BTeUxGTiIsInZjIjp7IkBjb250ZXh0IjpbImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL3YxIl0sImNyZWRlbnRpYWxTY2hlbWEiOlt7ImlkIjoiaHR0cHM6Ly9hcGktY29uZm9ybWFuY2UuZWJzaS5ldS90cnVzdGVkLXNjaGVtYXMtcmVnaXN0cnkvdjIvc2NoZW1hcy96M01nVUZVa2I3MjJ1cTR4M2R2NXlBSm1uTm16REZlSzVVQzh4ODNRb2VMSk0iLCJ0eXBlIjoiRnVsbEpzb25TY2hlbWFWYWxpZGF0b3IyMDIxIn1dLCJjcmVkZW50aWFsU3ViamVjdCI6eyJfc2QiOlsiWExKaGQzTXNVSFdMaDlCTFg0UWVlTmJ1RWJTR2REX2cwR0hQV0FGNjFBTSIsIjlwUndDdEdfM21qblUxdDRGcTU1SEJ1OWZmbTJZdUl4bGlHOWl6YmE1bFUiXSwiaWQiOiJkaWQ6a2V5OnoyZG16RDgxY2dQeDhWa2k3SmJ1dU1tRllyV1BnWW95dHlrVVozZXlxaHQxajlLYnE4ZWNOQUN0aW1kSmpBbWFUN0wyVkdySmVpV0V1RngxZFNmblBWU3ZQVUFKbmhvem5mNHBKdFhwOXM3b1k2TGhCTGhzRENFUE5HY01uaVNBeDgyd0ozazgzaXo3bk1BbXh0WG1xTlpSU01kWnlpTEEyODRWTkZCazM0N3NQU3lMRk4ifSwiZXhwaXJhdGlvbkRhdGUiOiIyMDI0LTA4LTE3VDIwOjQyOjMzWiIsImlkIjoidXJuOmRpZDoxMGRmYTM4MC0wODFlLTQxMDMtYWQ0Ni1hODU4NzUwZTJiOWQiLCJpc3N1YW5jZURhdGUiOiIyMDI0LTA3LTE4VDIwOjQyOjMzWiIsImlzc3VlZCI6IjIwMjQtMDctMThUMjA6NDI6MzNaIiwiaXNzdWVyIjoiZGlkOmtleTp6MmRtekQ4MWNnUHg4VmtpN0pidXVNbUZZcldQZ1lveXR5a1VaM2V5cWh0MWo5S2JwcFlGenU2eVE1ZnRwTGJudXhEU043dFpLb3B1M3BZcnN6b3hFd1FocWQ5SFZ2Z2U5SkZqZTViOU13elVvWDYxUHJDTWdzWGRDanRMQXNNN1ZCTmQ2aTUzdlNDaDlveDI3RExFR3cyblhNMkZpVlJOZG5pNXFIaUdlNHlENUNkQnJrIiwidHlwZSI6WyJWZXJpZmlhYmxlTGVnYWxQZXJzb25hbElkZW50aWZpY2F0aW9uRGF0YSJdLCJ2YWxpZEZyb20iOiIyMDI0LTA3LTE4VDIwOjQyOjMzWiJ9fQ.IRQebpnVr3vx8r8nrFKRnyGZltR_eDU4kgFxUa2sBoFr3RWu5ZBTtNLBVCOqT1EfawtA1ix1ScGRWZ5NJEkpFA~WyJiZDgwY2FlZWM2NzIxMWI2NGEwNTc4ZjViZDc5MDViMWQ1ZTRjZTJiMmI5Y2UxZDE1ODMxNzdjOTkwMDhjN2U5IiwiaWRlbnRpZmllciIsIjAwMCJd~WyJlM2U0YmY4ZmU4YThlNmM1NGU4ZjdkNjcwNDRlMDM1OTE0MWZmYTIwOWMzOGYzYTdlNTY1MmVkZTkxOGExMWE2IiwibGVnYWxOYW1lIiwiYWwiXQ" + }, + { + "0bd0cdd3-58c1-4192-a0b2-2853b7f4d283": "eyJhbGciOiJFUzI1NiIsImtpZCI6ImRpZDprZXk6ejJkbXpEODFjZ1B4OFZraTdKYnV1TW1GWXJXUGdZb3l0eWtVWjNleXFodDFqOUticHBZRnp1NnlRNWZ0cExibnV4RFNON3RaS29wdTNwWXJzem94RXdRaHFkOUhWdmdlOUpGamU1YjlNd3pVb1g2MVByQ01nc1hkQ2p0TEFzTTdWQk5kNmk1M3ZTQ2g5b3gyN0RMRUd3Mm5YTTJGaVZSTmRuaTVxSGlHZTR5RDVDZEJyayN6MmRtekQ4MWNnUHg4VmtpN0pidXVNbUZZcldQZ1lveXR5a1VaM2V5cWh0MWo5S2JwcFlGenU2eVE1ZnRwTGJudXhEU043dFpLb3B1M3BZcnN6b3hFd1FocWQ5SFZ2Z2U5SkZqZTViOU13elVvWDYxUHJDTWdzWGRDanRMQXNNN1ZCTmQ2aTUzdlNDaDlveDI3RExFR3cyblhNMkZpVlJOZG5pNXFIaUdlNHlENUNkQnJrIiwidHlwIjoiSldUIn0.eyJleHAiOjE3MjM5ODIxMDEsImlhdCI6MTcyMTM5MDEwMSwiaXNzIjoiZGlkOmtleTp6MmRtekQ4MWNnUHg4VmtpN0pidXVNbUZZcldQZ1lveXR5a1VaM2V5cWh0MWo5S2JwcFlGenU2eVE1ZnRwTGJudXhEU043dFpLb3B1M3BZcnN6b3hFd1FocWQ5SFZ2Z2U5SkZqZTViOU13elVvWDYxUHJDTWdzWGRDanRMQXNNN1ZCTmQ2aTUzdlNDaDlveDI3RExFR3cyblhNMkZpVlJOZG5pNXFIaUdlNHlENUNkQnJrIiwianRpIjoidXJuOmRpZDo5OTY3MTY1Mi03Zjg4LTQyMmQtOTg3Yi0wMTNmODE5NDg1OTAiLCJuYmYiOjE3MjEzOTAxMDEsInN1YiI6ImRpZDprZXk6ejJkbXpEODFjZ1B4OFZraTdKYnV1TW1GWXJXUGdZb3l0eWtVWjNleXFodDFqOUticThlY05BQ3RpbWRKakFtYVQ3TDJWR3JKZWlXRXVGeDFkU2ZuUFZTdlBVQUpuaG96bmY0cEp0WHA5czdvWTZMaEJMaHNEQ0VQTkdjTW5pU0F4ODJ3SjNrODNpejduTUFteHRYbXFOWlJTTWRaeWlMQTI4NFZORkJrMzQ3c1BTeUxGTiIsInZjIjp7IkBjb250ZXh0IjpbImh0dHBzOi8vd3d3LnczLm9yZy8yMDE4L2NyZWRlbnRpYWxzL3YxIl0sImNyZWRlbnRpYWxTY2hlbWEiOlt7ImlkIjoiaHR0cHM6Ly9hcGktY29uZm9ybWFuY2UuZWJzaS5ldS90cnVzdGVkLXNjaGVtYXMtcmVnaXN0cnkvdjIvc2NoZW1hcy96M01nVUZVa2I3MjJ1cTR4M2R2NXlBSm1uTm16REZlSzVVQzh4ODNRb2VMSk0iLCJ0eXBlIjoiRnVsbEpzb25TY2hlbWFWYWxpZGF0b3IyMDIxIn1dLCJjcmVkZW50aWFsU3ViamVjdCI6eyJfc2QiOlsiTUVmQUhQcXljamNndEdTLUZ4ZVQzcWhvQi1XUFNsQlNfWDhCSFNxSUYzRSIsIi14MEJ6WUV3dE80Nmd5bC12UFk5VTA2WHVVMG9uSWZuYjlzSXVvNUozZjAiLCJYaXNCS3dpVU5tS1NTbVFta3NiaUluR01vZHNQcDZHQnNGLWxmOVAtckU4IiwiN0ZBaDhGa2ZxcVpEa2N6X0dvd3UyejBNX1dsWV85ZXZyRTQ0aDFLdGtPVSIsIjNGNXl2bFpSMmN5UkMwaGh4QUhJMW51aThGMzZBZFQxUjF0dlM4Tk11S1kiLCJ0d0FPaUdvejF4SXdZdE5hX3hHWnJhTUgwclNSdFhtRHl0eXBzdGJCWGxZIl0sImlkIjoiZGlkOmtleTp6MmRtekQ4MWNnUHg4VmtpN0pidXVNbUZZcldQZ1lveXR5a1VaM2V5cWh0MWo5S2JxOGVjTkFDdGltZEpqQW1hVDdMMlZHckplaVdFdUZ4MWRTZm5QVlN2UFVBSm5ob3puZjRwSnRYcDlzN29ZNkxoQkxoc0RDRVBOR2NNbmlTQXg4MndKM2s4M2l6N25NQW14dFhtcU5aUlNNZFp5aUxBMjg0Vk5GQmszNDdzUFN5TEZOIiwibGVnYWxGb3JtIjoiYWRjIn0sImV4cGlyYXRpb25EYXRlIjoiMjAyNC0wOC0xOFQxMTo1NTowMVoiLCJpZCI6InVybjpkaWQ6OTk2NzE2NTItN2Y4OC00MjJkLTk4N2ItMDEzZjgxOTQ4NTkwIiwiaXNzdWFuY2VEYXRlIjoiMjAyNC0wNy0xOVQxMTo1NTowMVoiLCJpc3N1ZWQiOiIyMDI0LTA3LTE5VDExOjU1OjAxWiIsImlzc3VlciI6ImRpZDprZXk6ejJkbXpEODFjZ1B4OFZraTdKYnV1TW1GWXJXUGdZb3l0eWtVWjNleXFodDFqOUticHBZRnp1NnlRNWZ0cExibnV4RFNON3RaS29wdTNwWXJzem94RXdRaHFkOUhWdmdlOUpGamU1YjlNd3pVb1g2MVByQ01nc1hkQ2p0TEFzTTdWQk5kNmk1M3ZTQ2g5b3gyN0RMRUd3Mm5YTTJGaVZSTmRuaTVxSGlHZTR5RDVDZEJyayIsInR5cGUiOlsiVmVyaWZpYWJsZUNlcnRpZmljYXRlT2ZSZWdpc3RyYXRpb24iXSwidmFsaWRGcm9tIjoiMjAyNC0wNy0xOVQxMTo1NTowMVoifX0.JQYvx-ET6GmiqhIseNvvcyGNg2EYnIKyb0I4gF3InfDy2aadxGzCZNuvMSq2zKfcPFKkFiFYUdVQZqdHffamZw~WyI5ODQyNDEyOGU5MDA5YWI4MTJmMGZkOWUxNzdlYzY5NTRlNzIxNWMzMjcwMzIzNGRkM2M0MmRlN2RiODcxOWRhIiwibmFtZSIsInRlc3QiXQ~WyI3Y2ZiOWNmZmVmODFmNTRlYTQ4YjI4YzY2MTViYjQxZjNiMDhhNjJlYTQ1MWJkYWYxOGQ4MmExYTA2Mzk5OWIxIiwiYWN0aXZpdHkiLCJhYmMiXQ~WyI5Njc5YTU3YWJlMTQ5ZWRmZjQ2NDg1NDEwZTliZjU0NzE1ODJmZDJlYjYyZDhhMjY3ZDJmNWQ3Y2I1NzBkNzgzIiwicmVnaXN0cmF0aW9uRGF0ZSIsIjMyMSJd~WyJiY2QyZjVkNWU5NzM3Yjk2MDRkNGQ0NDU1YjJhODFhMTAzMjY5NDlhNzcwODQwMGY1YjYwMGI1MDQ5NTAzYTNmIiwibGVnYWxTdGF0dXMiLCJhc2FkZmEiXQ~WyI5NjMwZWE5ZDk4Yzk1MmFlNTRjOWU4YTQxZDY1YWIwNWFlNjNkODQ1Yzc3Y2IwNjQxNjc5Nzc2ZmFkN2UxY2E1IiwicmVnaXN0ZXJlZEFkZHJlc3MiLHsiYWRtaW5Vbml0TGV2ZWwxIjoiMSIsImZ1bGxBZGRyZXNzIjoiMiIsImxvY2F0b3JEZXNpZ25hdG9yIjoiMyIsInBvc3RDb2RlIjoiNCIsInBvc3ROYW1lIjoiNSIsInRob3JvdWdoRmFyZSI6IjYifV0~WyI0OTA3NjRmZmFhYTBkMDFlNzAwMjFjMjcxNTA4ZjU0YmUxNDU3ZmQ2YjJiMDg5MWYwNDJkZTExMTgwMTNiYmQzIiwib3JnTnVtYmVyIiwiMTIzIl0" + }, + ] + input_descriptor = json.dumps( + { + "constraints": { + "fields": [ + { + "filter": { + "contains": { + "const": "VerifiableCertificateOfRegistration" + }, + "type": "array", + }, + "path": ["$.type"], + }, + {"path": ["$.credentialSubject.name"]}, + {"path": ["$.credentialSubject.legalForm"]}, + {"path": ["$.credentialSubject.adminUnitLevel1"]}, + ], + "limit_disclosure": "required", + }, + "id": "95d57868-11c7-4726-870b-71b0a8af4cb1", + } + ) + matched_credentials = match_credentials_for_sd_jwt( + input_descriptor_json=input_descriptor, credentials=credentials + ) + + expected_matched_credentials = ([], None) + condition_1 = matched_credentials == expected_matched_credentials + self.assert_( + condition_1, + f"Expected matched credential doesn't match with result", + ) + + input_descriptor_2 = json.dumps( + { + "constraints": { + "fields": [ + { + "filter": { + "contains": { + "const": "VerifiableCertificateOfRegistration" + }, + "type": "array", + }, + "path": ["$.type"], + }, + {"path": ["$.credentialSubject.name"]}, + {"path": ["$.credentialSubject.legalForm"]}, + {"path": ["$.credentialSubject.registeredAddress"]}, + ], + "limit_disclosure": "required", + }, + "id": "95d57868-11c7-4726-870b-71b0a8af4cb1", + } + ) + matched_credentials = match_credentials_for_sd_jwt( + input_descriptor_json=input_descriptor_2, credentials=credentials + ) + + expected_matched_credentials = ( + [ + MatchedCredential( + index="0bd0cdd3-58c1-4192-a0b2-2853b7f4d283", + fields=[ + MatchedField( + index=0, + path=MatchedPath( + path="$.type", + index=0, + value=["VerifiableCertificateOfRegistration"], + ), + ), + MatchedField( + index=1, + path=MatchedPath( + path="$.credentialSubject.name", index=0, value="test" + ), + ), + MatchedField( + index=2, + path=MatchedPath( + path="$.credentialSubject.legalForm", + index=0, + value="adc", + ), + ), + MatchedField( + index=3, + path=MatchedPath( + path="$.credentialSubject.registeredAddress", + index=0, + value={ + "adminUnitLevel1": "1", + "fullAddress": "2", + "locatorDesignator": "3", + "postCode": "4", + "postName": "5", + "thoroughFare": "6", + }, + ), + ), + ], + ) + ], + None, + ) + condition_2 = matched_credentials == expected_matched_credentials + self.assert_( + condition_2, + f"Expected matched credential doesn't match with result", + ) + if __name__ == "__main__": unittest.main()