From 403992fe679db45ea3233c54ae8c117fa50c9d0a Mon Sep 17 00:00:00 2001 From: Char Howland Date: Mon, 14 Aug 2023 17:18:17 -0600 Subject: [PATCH 01/85] feat: add wallet_sd_jwt_sign endpoint Signed-off-by: Char Howland --- aries_cloudagent/wallet/routes.py | 39 +++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/aries_cloudagent/wallet/routes.py b/aries_cloudagent/wallet/routes.py index 20dd8727c1..66711ab8e5 100644 --- a/aries_cloudagent/wallet/routes.py +++ b/aries_cloudagent/wallet/routes.py @@ -50,6 +50,7 @@ from ..resolver.base import ResolverError from ..storage.error import StorageError, StorageNotFoundError from ..wallet.jwt import jwt_sign, jwt_verify +from ..wallet.sd_jwt import sd_jwt_sign from .base import BaseWallet from .did_info import DIDInfo from .did_method import KEY, SOV, DIDMethod, DIDMethods, HolderDefinedDid @@ -941,6 +942,43 @@ async def wallet_jwt_sign(request: web.BaseRequest): return web.json_response(jws) +@docs( + tags=["wallet"], summary="Create a EdDSA sd-jws using did keys with a given payload" +) +@request_schema(JWSCreateSchema) # check +@response_schema(WalletModuleResponseSchema(), description="") +async def wallet_sd_jwt_sign(request: web.BaseRequest): + """ + Request handler for sd-jws creation using did. + + Args: + "headers": { ... }, + "payload": { ... }, + "did": "did:example:123", + "verificationMethod": "did:example:123#keys-1" + with did and verification being mutually exclusive. + """ + context: AdminRequestContext = request["context"] + body = await request.json() + did = body.get("did") + verification_method = body.get("verificationMethod") + headers = body.get("headers", {}) + payload = body.get("payload", {}) + + try: + sd_jws = await sd_jwt_sign( + context.profile, headers, payload, did, verification_method + ) + except ValueError as err: + raise web.HTTPBadRequest(reason="Bad did or verification method") from err + except WalletNotFoundError as err: + raise web.HTTPNotFound(reason=err.roll_up) from err + except WalletError as err: + raise web.HTTPBadRequest(reason=err.roll_up) from err + + return web.json_response(sd_jws) + + @docs(tags=["wallet"], summary="Verify a EdDSA jws using did keys with a given JWS") @request_schema(JWSVerifySchema()) @response_schema(JWSVerifyResponseSchema(), 200, description="") @@ -1125,6 +1163,7 @@ async def register(app: web.Application): web.post("/wallet/set-did-endpoint", wallet_set_did_endpoint), web.post("/wallet/jwt/sign", wallet_jwt_sign), web.post("/wallet/jwt/verify", wallet_jwt_verify), + web.post("/wallet/sd-jwt/sign", wallet_sd_jwt_sign), web.get( "/wallet/get-did-endpoint", wallet_get_did_endpoint, allow_head=False ), From c753d551635a75b2018417d20ac0bcc6934fecad Mon Sep 17 00:00:00 2001 From: Char Howland Date: Mon, 14 Aug 2023 17:20:24 -0600 Subject: [PATCH 02/85] feat: (WIP) sd_jwt_sign logic using json paths Signed-off-by: Char Howland --- aries_cloudagent/wallet/sd_jwt.py | 103 ++++++++++++++++++++++++++++++ requirements.txt | 33 ++++++++++ 2 files changed, 136 insertions(+) create mode 100644 aries_cloudagent/wallet/sd_jwt.py create mode 100644 requirements.txt diff --git a/aries_cloudagent/wallet/sd_jwt.py b/aries_cloudagent/wallet/sd_jwt.py new file mode 100644 index 0000000000..d35a1bd25d --- /dev/null +++ b/aries_cloudagent/wallet/sd_jwt.py @@ -0,0 +1,103 @@ +"""Operations supporting SD-JWT creation and verification.""" + +import json +from typing import Any, List, Mapping, Optional +from jsonpath_ng.ext import parse +from sd_jwt.common import SDObj +from sd_jwt.issuer import SDJWTIssuer + +from ..core.profile import Profile +from ..wallet.jwt import jwt_sign +from ..core.error import BaseError + + +class SDJWTError(BaseError): + """SD-JWT Error""" + + +class SDJWTIssuerACAPy(SDJWTIssuer): + def __init__( + self, + user_claims, + issuer_key, + holder_key, + profile: Profile, + headers: dict, + did: Optional[str] = None, + verification_method: Optional[str] = None, + ): + super().__init__(user_claims, issuer_key, holder_key) + self.profile = profile + self.headers = headers + self.did = did + self.verification_method = verification_method + + def _create_signed_jws(self): + self.serialized_sd_jwt = "" + + return jwt_sign( + self.profile, + self.headers, + self.sd_jwt_payload, + self.did, + self.verification_method, + ) + + +def sort_sd_list(sd_list): + """ + Sorts sd list such that selectively disclosable claims deepest + in the structure are handled first. + """ + nested_claim_sort = [(len(sd.split(".")), sd) for sd in sd_list] + nested_claim_sort.sort(reverse=True) + return [sd[1] for sd in nested_claim_sort] + + +async def sd_jwt_sign( + profile: Profile, + headers: Mapping[str, Any], + payload: Mapping[str, Any], + sd_list: List, + did: Optional[str] = None, + verification_method: Optional[str] = None, +) -> str: + + sorted_sd_list = sort_sd_list(sd_list) + for sd in sorted_sd_list: + jsonpath_expression = parse(f"$.{sd}") + matches = jsonpath_expression.find(payload) + if len(matches) < 1: + raise SDJWTError("Claim for {sd} not found in payload.") + else: + for match in matches: + if type(match.context.value) is list: + match.context.value.remove(match.value) + match.context.value.append(SDObj(match.value)) + elif type(match.context.value) is str or int or dict or bool: + match.context.value[ + SDObj(str(match.path)) + ] = match.context.value.pop(str(match.path)) + else: + raise SDJWTError( + f"Unrecognized type {type(match.context.value)} for {match.path}" + ) + + sd_jwt_issuer = SDJWTIssuerACAPy( + user_claims=payload, + issuer_key=None, + holder_key=None, + profile=profile, + did=did, + verification_method=verification_method, + ) + + print("sd_jwt_payload: ", json.dumps(sd_jwt_issuer.sd_jwt_payload, indent=4)) + print("") + print("sd_jwt_issuer.ii_disclosures: ") + for sd in sd_jwt_issuer.ii_disclosures: + print(sd.key, sd.value) + print("") + print("sd_jwt_issuer.sd_jwt_issuance: ", sd_jwt_issuer.sd_jwt_issuance) + + return sd_jwt_issuer.sd_jwt_issuance diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000000..e12e200aa8 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,33 @@ +aiohttp~=3.8.1 +aiohttp-apispec~=2.2.1 +aiohttp-cors~=0.7.0 +aioredis~=2.0.0 +apispec~=3.3.0 +async-timeout~=4.0.2 +base58~=2.1.0 +ConfigArgParse~=1.5.3 +deepmerge~=0.3.0 +ecdsa~=0.16.1 +jsonpath_ng==1.5.2 +Markdown~=3.1.1 +markupsafe==2.0.1 +marshmallow~=3.20.1 +msgpack~=1.0 +multiformats~=0.2.1 +nest_asyncio~=1.5.5 +packaging~=23.1 +portalocker~=2.7.0 +prompt_toolkit~=2.0.9 +pydid~=0.3.6 +pyjwt~=2.8.0 +pyld~=2.0.3 +pynacl~=1.5.0 +python-dateutil~=2.8.1 +python-json-logger~=2.0.7 +pytz~=2021.1 +pyyaml~=6.0.1 +qrcode[pil]~=6.1 +requests~=2.31.0 +rlp==1.2.0 +unflatten~=0.1 +git+https://github.com/danielfett/sd-jwt.git@main From 469b931351e8260f71be5d3c6c8b423be6f070de Mon Sep 17 00:00:00 2001 From: Char Howland Date: Mon, 14 Aug 2023 17:26:05 -0600 Subject: [PATCH 03/85] test: (WIP) sd-jwt sign Signed-off-by: Char Howland --- aries_cloudagent/wallet/sd_jwt.py | 1 + aries_cloudagent/wallet/tests/test_sd_jwt.py | 77 ++++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 aries_cloudagent/wallet/tests/test_sd_jwt.py diff --git a/aries_cloudagent/wallet/sd_jwt.py b/aries_cloudagent/wallet/sd_jwt.py index d35a1bd25d..8722ce0e0c 100644 --- a/aries_cloudagent/wallet/sd_jwt.py +++ b/aries_cloudagent/wallet/sd_jwt.py @@ -88,6 +88,7 @@ async def sd_jwt_sign( issuer_key=None, holder_key=None, profile=profile, + headers=headers, did=did, verification_method=verification_method, ) diff --git a/aries_cloudagent/wallet/tests/test_sd_jwt.py b/aries_cloudagent/wallet/tests/test_sd_jwt.py new file mode 100644 index 0000000000..5fe21516b3 --- /dev/null +++ b/aries_cloudagent/wallet/tests/test_sd_jwt.py @@ -0,0 +1,77 @@ +from unittest import TestCase +import pytest +from aries_cloudagent.resolver.did_resolver import DIDResolver +from aries_cloudagent.resolver.tests.test_did_resolver import MockResolver +from aries_cloudagent.wallet.default_verification_key_strategy import ( + BaseVerificationKeyStrategy, + DefaultVerificationKeyStrategy, +) +from jsonpath_ng.ext import parse +import json + + +from ...core.in_memory.profile import InMemoryProfile +from ...wallet.did_method import KEY, DIDMethods +from ...wallet.key_type import ED25519 +from ...wallet.in_memory import InMemoryWallet + +from ..jwt import jwt_sign, jwt_verify, resolve_public_key_by_kid_for_verify +from ..sd_jwt import sd_jwt_sign + +from .test_jwt import profile, in_memory_wallet + + +class TestSDJWT: + """Tests for JWT sign and verify using dids.""" + + seed = "testseed000000000000000000000001" + + @pytest.mark.asyncio + async def test_sign_with_did_key_and_verify(self, profile, in_memory_wallet): + did_info = await in_memory_wallet.create_local_did(KEY, ED25519, self.seed) + did = did_info.did + verification_method = None + sd_list = [ + "address", + "address.street_address", + "address.street_address.house_number", + "address.locality", + "address.region", + "address.country", + "given_name", + "family_name", + "email", + "phone_number", + "phone_number_verified", + "birthdate", + "updated_at", + "nationalities[1:3]", + ] + headers = {} + payload = { + "sub": "user_42", + "given_name": "John", + "family_name": "Doe", + "email": "johndoe@example.com", + "phone_number": "+1-202-555-0101", + "phone_number_verified": True, + "address": { + "street_address": { + "house_number": "123", + "street": "Main St", + }, + "locality": "Anytown", + "region": "Anystate", + "country": "US", + }, + "birthdate": "1940-01-01", + "updated_at": 1570000000, + "nationalities": ["US", "DE", "SA"], + } + signed = await sd_jwt_sign( + profile, headers, payload, sd_list, did, verification_method + ) + + assert signed + + # assert await jwt_verify(profile, signed) From e8ec8c1ed99e59c559a256ea673ea6fbcc8c1da8 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Wed, 16 Aug 2023 10:02:07 -0600 Subject: [PATCH 04/85] fix: super().__init__() Signed-off-by: Char Howland --- aries_cloudagent/wallet/sd_jwt.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/aries_cloudagent/wallet/sd_jwt.py b/aries_cloudagent/wallet/sd_jwt.py index 8722ce0e0c..58838f2349 100644 --- a/aries_cloudagent/wallet/sd_jwt.py +++ b/aries_cloudagent/wallet/sd_jwt.py @@ -18,7 +18,7 @@ class SDJWTError(BaseError): class SDJWTIssuerACAPy(SDJWTIssuer): def __init__( self, - user_claims, + user_claims: dict, issuer_key, holder_key, profile: Profile, @@ -26,11 +26,11 @@ def __init__( did: Optional[str] = None, verification_method: Optional[str] = None, ): - super().__init__(user_claims, issuer_key, holder_key) self.profile = profile self.headers = headers self.did = did self.verification_method = verification_method + super().__init__(user_claims, issuer_key, holder_key) def _create_signed_jws(self): self.serialized_sd_jwt = "" @@ -61,7 +61,7 @@ async def sd_jwt_sign( sd_list: List, did: Optional[str] = None, verification_method: Optional[str] = None, -) -> str: +) -> SDJWTIssuerACAPy: sorted_sd_list = sort_sd_list(sd_list) for sd in sorted_sd_list: @@ -92,13 +92,6 @@ async def sd_jwt_sign( did=did, verification_method=verification_method, ) - - print("sd_jwt_payload: ", json.dumps(sd_jwt_issuer.sd_jwt_payload, indent=4)) - print("") - print("sd_jwt_issuer.ii_disclosures: ") - for sd in sd_jwt_issuer.ii_disclosures: - print(sd.key, sd.value) - print("") - print("sd_jwt_issuer.sd_jwt_issuance: ", sd_jwt_issuer.sd_jwt_issuance) + print(json.dumps(sd_jwt_issuer.sd_jwt_payload, indent=4)) return sd_jwt_issuer.sd_jwt_issuance From a1496fb65461c5fc425abcae90d6c6eff368586d Mon Sep 17 00:00:00 2001 From: Char Howland Date: Wed, 16 Aug 2023 11:59:41 -0600 Subject: [PATCH 05/85] fix: await jwt_sign() call Signed-off-by: Char Howland --- aries_cloudagent/wallet/sd_jwt.py | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/aries_cloudagent/wallet/sd_jwt.py b/aries_cloudagent/wallet/sd_jwt.py index 58838f2349..0986e57998 100644 --- a/aries_cloudagent/wallet/sd_jwt.py +++ b/aries_cloudagent/wallet/sd_jwt.py @@ -25,17 +25,25 @@ def __init__( headers: dict, did: Optional[str] = None, verification_method: Optional[str] = None, + add_decoy_claims: bool = False, + serialization_format: str = "compact", ): + self._user_claims = user_claims + self._issuer_key = issuer_key + self._holder_key = holder_key + self.profile = profile self.headers = headers self.did = did self.verification_method = verification_method - super().__init__(user_claims, issuer_key, holder_key) - def _create_signed_jws(self): - self.serialized_sd_jwt = "" + self._add_decoy_claims = add_decoy_claims + self._serialization_format = serialization_format + self.ii_disclosures = [] - return jwt_sign( + async def _create_signed_jws(self): + self.serialized_sd_jwt = "" + return await jwt_sign( self.profile, self.headers, self.sd_jwt_payload, @@ -43,6 +51,12 @@ def _create_signed_jws(self): self.verification_method, ) + async def issue(self): + self._check_for_sd_claim(self._user_claims) + self._assemble_sd_jwt_payload() + await self._create_signed_jws() + self._create_combined() + def sort_sd_list(sd_list): """ @@ -92,6 +106,7 @@ async def sd_jwt_sign( did=did, verification_method=verification_method, ) + await sd_jwt_issuer.issue() print(json.dumps(sd_jwt_issuer.sd_jwt_payload, indent=4)) return sd_jwt_issuer.sd_jwt_issuance From 471a819dc156de7b0c8e8869536a0c25a3e9721b Mon Sep 17 00:00:00 2001 From: Char Howland Date: Wed, 16 Aug 2023 12:23:13 -0600 Subject: [PATCH 06/85] feat: create SDJWSCreateSchema Signed-off-by: Char Howland --- aries_cloudagent/wallet/routes.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/aries_cloudagent/wallet/routes.py b/aries_cloudagent/wallet/routes.py index 66711ab8e5..e7e049fadd 100644 --- a/aries_cloudagent/wallet/routes.py +++ b/aries_cloudagent/wallet/routes.py @@ -172,6 +172,12 @@ class JWSCreateSchema(OpenAPISchema): ) +class SDJWSCreateSchema(JWSCreateSchema): + """Request schema to create an sd-jws with a particular DID.""" + + sd_list = fields.List() + + class JWSVerifySchema(OpenAPISchema): """Request schema to verify a jws created from a DID.""" @@ -945,7 +951,7 @@ async def wallet_jwt_sign(request: web.BaseRequest): @docs( tags=["wallet"], summary="Create a EdDSA sd-jws using did keys with a given payload" ) -@request_schema(JWSCreateSchema) # check +@request_schema(SDJWSCreateSchema) @response_schema(WalletModuleResponseSchema(), description="") async def wallet_sd_jwt_sign(request: web.BaseRequest): """ @@ -964,10 +970,11 @@ async def wallet_sd_jwt_sign(request: web.BaseRequest): verification_method = body.get("verificationMethod") headers = body.get("headers", {}) payload = body.get("payload", {}) + sd_list = body.get("sd)list", []) try: sd_jws = await sd_jwt_sign( - context.profile, headers, payload, did, verification_method + context.profile, headers, payload, sd_list, did, verification_method ) except ValueError as err: raise web.HTTPBadRequest(reason="Bad did or verification method") from err From 62dfabec5dad9f1ea3eeee3ffe928ebf213ce249 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Wed, 16 Aug 2023 13:10:11 -0600 Subject: [PATCH 07/85] fix: marshmallow error Signed-off-by: Char Howland --- aries_cloudagent/wallet/routes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aries_cloudagent/wallet/routes.py b/aries_cloudagent/wallet/routes.py index e7e049fadd..754cf3a664 100644 --- a/aries_cloudagent/wallet/routes.py +++ b/aries_cloudagent/wallet/routes.py @@ -175,7 +175,7 @@ class JWSCreateSchema(OpenAPISchema): class SDJWSCreateSchema(JWSCreateSchema): """Request schema to create an sd-jws with a particular DID.""" - sd_list = fields.List() + sd_list = fields.List(fields.Str) class JWSVerifySchema(OpenAPISchema): From d4b36e77e2c686738d59ed59c8a2366e059aec99 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Wed, 16 Aug 2023 13:48:33 -0600 Subject: [PATCH 08/85] fix: flake8 Signed-off-by: Char Howland --- aries_cloudagent/wallet/sd_jwt.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/aries_cloudagent/wallet/sd_jwt.py b/aries_cloudagent/wallet/sd_jwt.py index 0986e57998..d10ae3f9bd 100644 --- a/aries_cloudagent/wallet/sd_jwt.py +++ b/aries_cloudagent/wallet/sd_jwt.py @@ -12,10 +12,12 @@ class SDJWTError(BaseError): - """SD-JWT Error""" + """SD-JWT Error.""" class SDJWTIssuerACAPy(SDJWTIssuer): + """SDJWTIssuer class for ACA-Py implementation.""" + def __init__( self, user_claims: dict, @@ -28,6 +30,7 @@ def __init__( add_decoy_claims: bool = False, serialization_format: str = "compact", ): + """Initalize an SDJWTIssuerACAPy instance.""" self._user_claims = user_claims self._issuer_key = issuer_key self._holder_key = holder_key @@ -52,6 +55,7 @@ async def _create_signed_jws(self): ) async def issue(self): + """Issue an sd-jwt.""" self._check_for_sd_claim(self._user_claims) self._assemble_sd_jwt_payload() await self._create_signed_jws() @@ -60,7 +64,9 @@ async def issue(self): def sort_sd_list(sd_list): """ - Sorts sd list such that selectively disclosable claims deepest + Sorts sd_list. + + Ensures that selectively disclosable claims deepest in the structure are handled first. """ nested_claim_sort = [(len(sd.split(".")), sd) for sd in sd_list] @@ -75,7 +81,14 @@ async def sd_jwt_sign( sd_list: List, did: Optional[str] = None, verification_method: Optional[str] = None, -) -> SDJWTIssuerACAPy: +) -> str: + """ + Sign sd-jwt. + + Use sd_list to wrap selectively disclosable claims with + SDObj within payload, create SDJWTIssuerACAPy object, and + call SDJWTIssuerACAPy.issue(). + """ sorted_sd_list = sort_sd_list(sd_list) for sd in sorted_sd_list: From 1615b4fe188948089997b6cfd34c32edd0288c02 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Wed, 16 Aug 2023 16:44:14 -0600 Subject: [PATCH 09/85] fix: typo Signed-off-by: Char Howland --- aries_cloudagent/wallet/routes.py | 2 +- aries_cloudagent/wallet/sd_jwt.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/aries_cloudagent/wallet/routes.py b/aries_cloudagent/wallet/routes.py index 754cf3a664..3e0614c7c8 100644 --- a/aries_cloudagent/wallet/routes.py +++ b/aries_cloudagent/wallet/routes.py @@ -175,7 +175,7 @@ class JWSCreateSchema(OpenAPISchema): class SDJWSCreateSchema(JWSCreateSchema): """Request schema to create an sd-jws with a particular DID.""" - sd_list = fields.List(fields.Str) + sd_list = fields.List(fields.Str()) class JWSVerifySchema(OpenAPISchema): diff --git a/aries_cloudagent/wallet/sd_jwt.py b/aries_cloudagent/wallet/sd_jwt.py index d10ae3f9bd..2005611c1d 100644 --- a/aries_cloudagent/wallet/sd_jwt.py +++ b/aries_cloudagent/wallet/sd_jwt.py @@ -30,7 +30,7 @@ def __init__( add_decoy_claims: bool = False, serialization_format: str = "compact", ): - """Initalize an SDJWTIssuerACAPy instance.""" + """Initialize an SDJWTIssuerACAPy instance.""" self._user_claims = user_claims self._issuer_key = issuer_key self._holder_key = holder_key From 14cee83824e02784990417b758584d44b49ad38d Mon Sep 17 00:00:00 2001 From: Char Howland Date: Thu, 17 Aug 2023 13:24:15 -0600 Subject: [PATCH 10/85] fix: typo Signed-off-by: Char Howland --- aries_cloudagent/wallet/jwt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aries_cloudagent/wallet/jwt.py b/aries_cloudagent/wallet/jwt.py index e66049a30a..fa9580e856 100644 --- a/aries_cloudagent/wallet/jwt.py +++ b/aries_cloudagent/wallet/jwt.py @@ -120,7 +120,7 @@ async def resolve_public_key_by_kid_for_verify(profile: Profile, kid: str) -> st async def jwt_verify(profile: Profile, jwt: str) -> JWTVerifyResult: """Verify a JWT and return the headers and payload.""" - encoded_headers, encoded_payload, encoded_signiture = jwt.split(".", 3) + encoded_headers, encoded_payload, encoded_signature = jwt.split(".", 3) headers = b64_to_dict(encoded_headers) if "alg" not in headers or headers["alg"] != "EdDSA" or "kid" not in headers: raise BadJWSHeaderError( @@ -129,7 +129,7 @@ async def jwt_verify(profile: Profile, jwt: str) -> JWTVerifyResult: payload = b64_to_dict(encoded_payload) verification_method = headers["kid"] - decoded_signature = b64_to_bytes(encoded_signiture, urlsafe=True) + decoded_signature = b64_to_bytes(encoded_signature, urlsafe=True) async with profile.session() as session: verkey = await resolve_public_key_by_kid_for_verify( From 04086df35a8c970f176e7db0666b8f33986ac324 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Thu, 17 Aug 2023 13:26:20 -0600 Subject: [PATCH 11/85] fix: set jwt_sign result on self.serialized_sd_jwt Signed-off-by: Char Howland --- aries_cloudagent/wallet/sd_jwt.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/aries_cloudagent/wallet/sd_jwt.py b/aries_cloudagent/wallet/sd_jwt.py index 2005611c1d..ae6b19df9f 100644 --- a/aries_cloudagent/wallet/sd_jwt.py +++ b/aries_cloudagent/wallet/sd_jwt.py @@ -45,8 +45,7 @@ def __init__( self.ii_disclosures = [] async def _create_signed_jws(self): - self.serialized_sd_jwt = "" - return await jwt_sign( + self.serialized_sd_jwt = await jwt_sign( self.profile, self.headers, self.sd_jwt_payload, From cc36294d05b600a7b29cdbc0db8800c12ae0312d Mon Sep 17 00:00:00 2001 From: Char Howland Date: Thu, 17 Aug 2023 13:30:32 -0600 Subject: [PATCH 12/85] feat: create endpoint for sd-jwt verify Signed-off-by: Char Howland --- aries_cloudagent/wallet/routes.py | 33 ++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/aries_cloudagent/wallet/routes.py b/aries_cloudagent/wallet/routes.py index 3e0614c7c8..693077d3d4 100644 --- a/aries_cloudagent/wallet/routes.py +++ b/aries_cloudagent/wallet/routes.py @@ -50,7 +50,7 @@ from ..resolver.base import ResolverError from ..storage.error import StorageError, StorageNotFoundError from ..wallet.jwt import jwt_sign, jwt_verify -from ..wallet.sd_jwt import sd_jwt_sign +from ..wallet.sd_jwt import sd_jwt_sign, sd_jwt_verify from .base import BaseWallet from .did_info import DIDInfo from .did_method import KEY, SOV, DIDMethod, DIDMethods, HolderDefinedDid @@ -1015,6 +1015,36 @@ async def wallet_jwt_verify(request: web.BaseRequest): ) +@docs(tags=["wallet"], summary="Verify a EdDSA sd-jws using did keys with a given JWS") +@request_schema(JWSVerifySchema()) +@response_schema(JWSVerifyResponseSchema(), 200, description="") +async def wallet_sd_jwt_verify(request: web.BaseRequest): + """ + Request handler for sd-jws validation using did. + + Args: + "sd-jwt": { ... } + """ + context: AdminRequestContext = request["context"] + body = await request.json() + sd_jwt = body["sd_jwt"] + try: + result = await sd_jwt_verify(context.profile, sd_jwt) + except (BadJWSHeaderError, InvalidVerificationMethod) as err: + raise web.HTTPBadRequest(reason=err.roll_up) from err + except ResolverError as err: + raise web.HTTPNotFound(reason=err.roll_up) from err + + return web.json_response( + { + "valid": result.valid, + "headers": result.headers, + "payload": result.payload, + "kid": result.kid, + } + ) + + @docs(tags=["wallet"], summary="Query DID endpoint in wallet") @querystring_schema(DIDQueryStringSchema()) @response_schema(DIDEndpointSchema, 200, description="") @@ -1171,6 +1201,7 @@ async def register(app: web.Application): web.post("/wallet/jwt/sign", wallet_jwt_sign), web.post("/wallet/jwt/verify", wallet_jwt_verify), web.post("/wallet/sd-jwt/sign", wallet_sd_jwt_sign), + web.post("/wallet/sd-jwt/verify", wallet_sd_jwt_verify), web.get( "/wallet/get-did-endpoint", wallet_get_did_endpoint, allow_head=False ), From 03f7c44bfe2b8a5510e30bb27cf9b591157ff2a2 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Thu, 17 Aug 2023 13:32:15 -0600 Subject: [PATCH 13/85] feat: create SDJWTVerifierACAPy class and sd_jwt_verify() method Signed-off-by: Char Howland --- aries_cloudagent/wallet/sd_jwt.py | 43 ++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/aries_cloudagent/wallet/sd_jwt.py b/aries_cloudagent/wallet/sd_jwt.py index ae6b19df9f..9cbde95e44 100644 --- a/aries_cloudagent/wallet/sd_jwt.py +++ b/aries_cloudagent/wallet/sd_jwt.py @@ -5,9 +5,10 @@ from jsonpath_ng.ext import parse from sd_jwt.common import SDObj from sd_jwt.issuer import SDJWTIssuer +from sd_jwt.verifier import SDJWTVerifier from ..core.profile import Profile -from ..wallet.jwt import jwt_sign +from ..wallet.jwt import JWTVerifyResult, jwt_sign, jwt_verify from ..core.error import BaseError @@ -122,3 +123,43 @@ async def sd_jwt_sign( print(json.dumps(sd_jwt_issuer.sd_jwt_payload, indent=4)) return sd_jwt_issuer.sd_jwt_issuance + + +class SDJWTVerifierACAPy(SDJWTVerifier): + def __init__( + self, + profile: Profile, + sd_jwt_presentation: str, + serialization_format: str = "compact", + ): + self.profile = profile + self.sd_jwt_presentation = sd_jwt_presentation + self.jwt = "" + self._serialization_format = serialization_format + + async def _verify_sd_jwt(self): + self.serialized_sd_jwt = "" + return await jwt_verify( + self.profile, + self.jwt, + ) + + def _parse_sd_jwt(self, sd_jwt): + if self._serialization_format == "compact": + ( + self._unverified_input_sd_jwt, + *self._input_disclosures, + self._unverified_input_key_binding_jwt, + ) = self._split(sd_jwt) + return self._unverified_input_sd_jwt + + async def verify(self): + self.jwt = self._parse_sd_jwt(self.sd_jwt_presentation) + self._create_hash_mappings(self._input_disclosures) + return await self._verify_sd_jwt() + + +async def sd_jwt_verify(profile: Profile, sd_jwt_presentation: str) -> JWTVerifyResult: + sd_jwt_verifier = SDJWTVerifierACAPy(profile, sd_jwt_presentation) + verified = await sd_jwt_verifier.verify() + return verified.valid From cea0f152dcc02ab7270585a9708393dcc73ddf41 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Thu, 17 Aug 2023 13:32:53 -0600 Subject: [PATCH 14/85] test: sd_jwt_verify() Signed-off-by: Char Howland --- aries_cloudagent/wallet/tests/test_sd_jwt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aries_cloudagent/wallet/tests/test_sd_jwt.py b/aries_cloudagent/wallet/tests/test_sd_jwt.py index 5fe21516b3..64e4f858b2 100644 --- a/aries_cloudagent/wallet/tests/test_sd_jwt.py +++ b/aries_cloudagent/wallet/tests/test_sd_jwt.py @@ -16,7 +16,7 @@ from ...wallet.in_memory import InMemoryWallet from ..jwt import jwt_sign, jwt_verify, resolve_public_key_by_kid_for_verify -from ..sd_jwt import sd_jwt_sign +from ..sd_jwt import sd_jwt_sign, sd_jwt_verify from .test_jwt import profile, in_memory_wallet @@ -74,4 +74,4 @@ async def test_sign_with_did_key_and_verify(self, profile, in_memory_wallet): assert signed - # assert await jwt_verify(profile, signed) + assert await sd_jwt_verify(profile, signed) From 0e013995d429668e9d6b34689a7eeefd5daa8818 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Thu, 17 Aug 2023 13:40:56 -0600 Subject: [PATCH 15/85] fix: remove self.jwt Signed-off-by: Char Howland --- aries_cloudagent/wallet/sd_jwt.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/aries_cloudagent/wallet/sd_jwt.py b/aries_cloudagent/wallet/sd_jwt.py index 9cbde95e44..3bbe4ed6e5 100644 --- a/aries_cloudagent/wallet/sd_jwt.py +++ b/aries_cloudagent/wallet/sd_jwt.py @@ -134,14 +134,12 @@ def __init__( ): self.profile = profile self.sd_jwt_presentation = sd_jwt_presentation - self.jwt = "" self._serialization_format = serialization_format async def _verify_sd_jwt(self): - self.serialized_sd_jwt = "" return await jwt_verify( self.profile, - self.jwt, + self.serialized_sd_jwt, ) def _parse_sd_jwt(self, sd_jwt): @@ -154,7 +152,7 @@ def _parse_sd_jwt(self, sd_jwt): return self._unverified_input_sd_jwt async def verify(self): - self.jwt = self._parse_sd_jwt(self.sd_jwt_presentation) + self.serialized_sd_jwt = self._parse_sd_jwt(self.sd_jwt_presentation) self._create_hash_mappings(self._input_disclosures) return await self._verify_sd_jwt() From d06286bde930f7706afd72d13acac157aee114d8 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Thu, 17 Aug 2023 16:49:10 -0600 Subject: [PATCH 16/85] feat: create SDJWSVerifyResponseSchema Signed-off-by: Char Howland --- aries_cloudagent/messaging/valid.py | 95 +++++++++++++++++++++++++++++ aries_cloudagent/wallet/routes.py | 40 ++++++++++-- 2 files changed, 131 insertions(+), 4 deletions(-) diff --git a/aries_cloudagent/messaging/valid.py b/aries_cloudagent/messaging/valid.py index 73e66af0d0..ef08fcdd66 100644 --- a/aries_cloudagent/messaging/valid.py +++ b/aries_cloudagent/messaging/valid.py @@ -200,6 +200,29 @@ def __init__(self): ) +class SDList(Regexp): + """Validate SD List""" + + EXAMPLE = [ + "given_name", + "family_name", + "address", + "address.street_address", + "address.street_address.house_number", + "email", + "nationalities[1:3]", + ] + PATTERN = r".*" # TODO: add Regex + + def __init__(self): + """Initialize the instance.""" + + super().__init__( + SDList.PATTERN, + error="Value {input} is not a valid SDList", + ) + + class JSONWebToken(Regexp): """Validate JSON Web Token.""" @@ -219,6 +242,69 @@ def __init__(self): ) +class SDJSONWebToken(Regexp): + """Validate SD-JSON Web Token.""" + + EXAMPLE = ( + "eyJhbGciOiJFZERTQSJ9." + "eyJhIjogIjAifQ." + "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk" + "~WyJEM3BUSFdCYWNRcFdpREc2TWZKLUZnIiwgIkRFIl0" + "~WyJPMTFySVRjRTdHcXExYW9oRkd0aDh3IiwgIlNBIl0" + "~WyJkVmEzX1JlTGNsWTU0R1FHZm5oWlRnIiwgInVwZGF0ZWRfYXQiLCAxNTcwMDAwMDAwXQ" + ) + PATTERN = r".*" # TODO: add Regex + + def __init__(self): + """Initialize the instance.""" + + super().__init__( + SDJSONWebToken.PATTERN, + error="Value {input} is not a valid SD-JSON Web token", + ) + + +class Disclosures(Regexp): + """Validate SD Disclosures.""" + + EXAMPLE = [ + ["uR_7gCub2H8jqCebK974gg", "given_name", "John"], + ["pVNC8_UIAFubHCE8qd8Qag", "family_name", "Doe"], + [ + "wn7kQK6gc1xjK2EUvdBx_w", + "address", + { + "_sd": [ + "62Z6rlJFKNagx_zCgTKdEKVGx5oeCGmFOBYP_V3nvXM", + "BJfn94_yMmEe3SsqdWwaPtWL0K92Q2AEEOKCIW0_ijI", + "W5Pb28JILuWXn3MzMybIREwEMo92ho07zAO_Zfp6UW4", + "XNseAXkWgm9P5cc7D5RPu3mA8uYuFWtK9vxwTQQZVW8", + ] + }, + ], + [ + "QHp6MA7HoUE3Ajan3LCqqw", + "street_address", + { + "_sd": ["5sYxNNvht8a9g5JON9yQWf4eIyogMtV7_7Y1VWh9QGU"], + "street": "Main St", + }, + ], + ["pFlIYzaDRUqagcTPlHD1-Q", "house_number", "123"], + ["pvyM3ymTZWjA4N6z_u7ufA", "email", "johndoe@example.com"], + ["P4vbBZ2_7t12LF1DKsSSMQ", "DE"], + ["jljUb3RcAszB__K8e0WP-Q", "SA"], + ] + PATTERN = r".*" # TODO: add Regex + + def __init__(self): + """Initialize the instance.""" + + super().__init__( + Disclosures.PATTERN, error="Value {input} is not a valid disclosure" + ) + + class DIDKey(Regexp): """Validate value against DID key specification.""" @@ -800,9 +886,18 @@ def __init__( JWS_HEADER_KID_VALIDATE = JWSHeaderKid() JWS_HEADER_KID_EXAMPLE = JWSHeaderKid.EXAMPLE +# SD_LIST_VALIDATE = SDList() +# SD_LIST_EXAMPLE = SDList().EXAMPLE + JWT_VALIDATE = JSONWebToken() JWT_EXAMPLE = JSONWebToken.EXAMPLE +SD_JWT_VALIDATE = SDJSONWebToken() +SD_JWT_EXAMPLE = SDJSONWebToken.EXAMPLE + +DISCLOSURES_VALIDATE = Disclosures() +DISCLOSURES_EXAMPLE = Disclosures().EXAMPLE + DID_KEY_VALIDATE = DIDKey() DID_KEY_EXAMPLE = DIDKey.EXAMPLE diff --git a/aries_cloudagent/wallet/routes.py b/aries_cloudagent/wallet/routes.py index 693077d3d4..5319984bff 100644 --- a/aries_cloudagent/wallet/routes.py +++ b/aries_cloudagent/wallet/routes.py @@ -23,6 +23,8 @@ from ..messaging.valid import ( DID_POSTURE_EXAMPLE, DID_POSTURE_VALIDATE, + DISCLOSURES_EXAMPLE, + DISCLOSURES_VALIDATE, ENDPOINT_EXAMPLE, ENDPOINT_TYPE_EXAMPLE, ENDPOINT_TYPE_VALIDATE, @@ -35,6 +37,10 @@ INDY_RAW_PUBLIC_KEY_VALIDATE, JWT_EXAMPLE, JWT_VALIDATE, + SD_JWT_EXAMPLE, + SD_JWT_VALIDATE, + SD_LIST_EXAMPLE, + SD_LIST_VALIDATE, IndyDID, Uri, ) @@ -175,7 +181,13 @@ class JWSCreateSchema(OpenAPISchema): class SDJWSCreateSchema(JWSCreateSchema): """Request schema to create an sd-jws with a particular DID.""" - sd_list = fields.List(fields.Str()) + sd_list = fields.List( + fields.Str( + required=False, + validate=SD_LIST_VALIDATE, + metadata={"example": SD_LIST_EXAMPLE}, + ) + ) class JWSVerifySchema(OpenAPISchema): @@ -184,8 +196,14 @@ class JWSVerifySchema(OpenAPISchema): jwt = fields.Str(validate=JWT_VALIDATE, metadata={"example": JWT_EXAMPLE}) +class SDJWSVerifySchema(OpenAPISchema): + """Request schema to verify an sd-jws created from a DID.""" + + sd_jwt = fields.Str(validate=SD_JWT_VALIDATE, metadata={"example": SD_JWT_EXAMPLE}) + + class JWSVerifyResponseSchema(OpenAPISchema): - """Response schema for verification result.""" + """Response schema for JWT verification result.""" valid = fields.Bool(required=True) error = fields.Str(required=False, metadata={"description": "Error text"}) @@ -198,6 +216,20 @@ class JWSVerifyResponseSchema(OpenAPISchema): ) +class SDJWSVerifyResponseSchema(JWSVerifyResponseSchema): + """Response schema for SD-JWT verification result.""" + + disclosures = fields.List( + fields.List( + fields.Str( + required=False, + validate=DISCLOSURES_VALIDATE, + metadata={"example": DISCLOSURES_EXAMPLE}, + ) + ) + ) + + class DIDEndpointSchema(OpenAPISchema): """Request schema to set DID endpoint; response schema to get DID endpoint.""" @@ -1016,8 +1048,8 @@ async def wallet_jwt_verify(request: web.BaseRequest): @docs(tags=["wallet"], summary="Verify a EdDSA sd-jws using did keys with a given JWS") -@request_schema(JWSVerifySchema()) -@response_schema(JWSVerifyResponseSchema(), 200, description="") +@request_schema(SDJWSVerifySchema()) +@response_schema(SDJWSVerifyResponseSchema(), 200, description="") async def wallet_sd_jwt_verify(request: web.BaseRequest): """ Request handler for sd-jws validation using did. From f1f3fdf3e2c3cc5fdc6c69c4a21a9ac15eb8eb9b Mon Sep 17 00:00:00 2001 From: Char Howland Date: Thu, 17 Aug 2023 16:50:29 -0600 Subject: [PATCH 17/85] feat: create SDJWTVerifyResult Signed-off-by: Char Howland --- aries_cloudagent/wallet/sd_jwt.py | 50 +++++++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 6 deletions(-) diff --git a/aries_cloudagent/wallet/sd_jwt.py b/aries_cloudagent/wallet/sd_jwt.py index 3bbe4ed6e5..fbef87d953 100644 --- a/aries_cloudagent/wallet/sd_jwt.py +++ b/aries_cloudagent/wallet/sd_jwt.py @@ -1,14 +1,14 @@ """Operations supporting SD-JWT creation and verification.""" import json -from typing import Any, List, Mapping, Optional +from typing import Any, List, Mapping, NamedTuple, Optional from jsonpath_ng.ext import parse from sd_jwt.common import SDObj from sd_jwt.issuer import SDJWTIssuer from sd_jwt.verifier import SDJWTVerifier from ..core.profile import Profile -from ..wallet.jwt import JWTVerifyResult, jwt_sign, jwt_verify +from ..wallet.jwt import jwt_sign, jwt_verify from ..core.error import BaseError @@ -125,22 +125,43 @@ async def sd_jwt_sign( return sd_jwt_issuer.sd_jwt_issuance +class SDJWTVerifyResult(NamedTuple): + """Result from verifying SD-JWT""" + + headers: Mapping[str, Any] + payload: Mapping[str, Any] + valid: bool + kid: str + disclosures: str + # TODO: figure out inheritance + + class SDJWTVerifierACAPy(SDJWTVerifier): + """SDJWTVerifier class for ACA-Py implementation.""" + def __init__( self, profile: Profile, sd_jwt_presentation: str, serialization_format: str = "compact", ): + """Initialize an SDJWTVerifierACAPy instance.""" self.profile = profile self.sd_jwt_presentation = sd_jwt_presentation self._serialization_format = serialization_format - async def _verify_sd_jwt(self): - return await jwt_verify( + async def _verify_sd_jwt(self) -> SDJWTVerifyResult: + verified = await jwt_verify( self.profile, self.serialized_sd_jwt, ) + return SDJWTVerifyResult( + headers=verified.headers, + payload=verified.payload, + valid=verified.valid, + kid=verified.kid, + disclosures=self._disclosures_list, + ) def _parse_sd_jwt(self, sd_jwt): if self._serialization_format == "compact": @@ -150,14 +171,31 @@ def _parse_sd_jwt(self, sd_jwt): self._unverified_input_key_binding_jwt, ) = self._split(sd_jwt) return self._unverified_input_sd_jwt + # TOOD: what to do about the else? + + def _create_disclosures_list(self) -> List: + disclosures_list = [] + for disclosure in self._input_disclosures: + disclosures_list.append( + json.loads(self._base64url_decode(disclosure).decode("utf-8")) + ) + + return disclosures_list async def verify(self): + """Verify an sd-jwt.""" self.serialized_sd_jwt = self._parse_sd_jwt(self.sd_jwt_presentation) self._create_hash_mappings(self._input_disclosures) + self._disclosures_list = self._create_disclosures_list() return await self._verify_sd_jwt() -async def sd_jwt_verify(profile: Profile, sd_jwt_presentation: str) -> JWTVerifyResult: +async def sd_jwt_verify( + profile: Profile, sd_jwt_presentation: str +) -> SDJWTVerifyResult: + """ + Verify sd-jwt using SDJWTVerifierACAPy.verify(). + """ sd_jwt_verifier = SDJWTVerifierACAPy(profile, sd_jwt_presentation) verified = await sd_jwt_verifier.verify() - return verified.valid + return verified From 9583624d8642f0d424b5955c51d1009f0adab6ea Mon Sep 17 00:00:00 2001 From: Char Howland Date: Mon, 21 Aug 2023 15:33:17 -0600 Subject: [PATCH 18/85] feat: sdlist regex (WIP) Signed-off-by: Char Howland --- aries_cloudagent/messaging/valid.py | 11 ++++------- aries_cloudagent/wallet/routes.py | 2 +- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/aries_cloudagent/messaging/valid.py b/aries_cloudagent/messaging/valid.py index ef08fcdd66..86c79dfe45 100644 --- a/aries_cloudagent/messaging/valid.py +++ b/aries_cloudagent/messaging/valid.py @@ -204,15 +204,12 @@ class SDList(Regexp): """Validate SD List""" EXAMPLE = [ - "given_name", - "family_name", + "name", "address", "address.street_address", - "address.street_address.house_number", - "email", "nationalities[1:3]", ] - PATTERN = r".*" # TODO: add Regex + PATTERN = r"[a-z0-9:\[\]_\.@?\(\)]" # TODO: check def __init__(self): """Initialize the instance.""" @@ -886,8 +883,8 @@ def __init__( JWS_HEADER_KID_VALIDATE = JWSHeaderKid() JWS_HEADER_KID_EXAMPLE = JWSHeaderKid.EXAMPLE -# SD_LIST_VALIDATE = SDList() -# SD_LIST_EXAMPLE = SDList().EXAMPLE +SD_LIST_VALIDATE = SDList() +SD_LIST_EXAMPLE = SDList().EXAMPLE JWT_VALIDATE = JSONWebToken() JWT_EXAMPLE = JSONWebToken.EXAMPLE diff --git a/aries_cloudagent/wallet/routes.py b/aries_cloudagent/wallet/routes.py index 5319984bff..d7ba16a999 100644 --- a/aries_cloudagent/wallet/routes.py +++ b/aries_cloudagent/wallet/routes.py @@ -1002,7 +1002,7 @@ async def wallet_sd_jwt_sign(request: web.BaseRequest): verification_method = body.get("verificationMethod") headers = body.get("headers", {}) payload = body.get("payload", {}) - sd_list = body.get("sd)list", []) + sd_list = body.get("sd_list", []) try: sd_jws = await sd_jwt_sign( From 212e9cae5ce0b64f130d1a5d76099c95aa08bde9 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Mon, 21 Aug 2023 15:34:17 -0600 Subject: [PATCH 19/85] feat: sd-jwt regex validation Signed-off-by: Char Howland --- aries_cloudagent/messaging/valid.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aries_cloudagent/messaging/valid.py b/aries_cloudagent/messaging/valid.py index 86c79dfe45..a255724418 100644 --- a/aries_cloudagent/messaging/valid.py +++ b/aries_cloudagent/messaging/valid.py @@ -250,7 +250,7 @@ class SDJSONWebToken(Regexp): "~WyJPMTFySVRjRTdHcXExYW9oRkd0aDh3IiwgIlNBIl0" "~WyJkVmEzX1JlTGNsWTU0R1FHZm5oWlRnIiwgInVwZGF0ZWRfYXQiLCAxNTcwMDAwMDAwXQ" ) - PATTERN = r".*" # TODO: add Regex + PATTERN = r"^[-_a-zA-Z0-9]*\.[-_a-zA-Z0-9]*\.[-_a-zA-Z0-9]*([~Wy][a-zA-Z0-9]*)*$" def __init__(self): """Initialize the instance.""" From c87f1b8f93d69b6be0c3e82fe7cb79225e125dc9 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Mon, 21 Aug 2023 15:35:42 -0600 Subject: [PATCH 20/85] feat: remove disclosures validation Signed-off-by: Char Howland --- aries_cloudagent/messaging/valid.py | 44 ----------------------------- aries_cloudagent/wallet/routes.py | 12 +------- 2 files changed, 1 insertion(+), 55 deletions(-) diff --git a/aries_cloudagent/messaging/valid.py b/aries_cloudagent/messaging/valid.py index a255724418..7c7178a49c 100644 --- a/aries_cloudagent/messaging/valid.py +++ b/aries_cloudagent/messaging/valid.py @@ -261,47 +261,6 @@ def __init__(self): ) -class Disclosures(Regexp): - """Validate SD Disclosures.""" - - EXAMPLE = [ - ["uR_7gCub2H8jqCebK974gg", "given_name", "John"], - ["pVNC8_UIAFubHCE8qd8Qag", "family_name", "Doe"], - [ - "wn7kQK6gc1xjK2EUvdBx_w", - "address", - { - "_sd": [ - "62Z6rlJFKNagx_zCgTKdEKVGx5oeCGmFOBYP_V3nvXM", - "BJfn94_yMmEe3SsqdWwaPtWL0K92Q2AEEOKCIW0_ijI", - "W5Pb28JILuWXn3MzMybIREwEMo92ho07zAO_Zfp6UW4", - "XNseAXkWgm9P5cc7D5RPu3mA8uYuFWtK9vxwTQQZVW8", - ] - }, - ], - [ - "QHp6MA7HoUE3Ajan3LCqqw", - "street_address", - { - "_sd": ["5sYxNNvht8a9g5JON9yQWf4eIyogMtV7_7Y1VWh9QGU"], - "street": "Main St", - }, - ], - ["pFlIYzaDRUqagcTPlHD1-Q", "house_number", "123"], - ["pvyM3ymTZWjA4N6z_u7ufA", "email", "johndoe@example.com"], - ["P4vbBZ2_7t12LF1DKsSSMQ", "DE"], - ["jljUb3RcAszB__K8e0WP-Q", "SA"], - ] - PATTERN = r".*" # TODO: add Regex - - def __init__(self): - """Initialize the instance.""" - - super().__init__( - Disclosures.PATTERN, error="Value {input} is not a valid disclosure" - ) - - class DIDKey(Regexp): """Validate value against DID key specification.""" @@ -892,9 +851,6 @@ def __init__( SD_JWT_VALIDATE = SDJSONWebToken() SD_JWT_EXAMPLE = SDJSONWebToken.EXAMPLE -DISCLOSURES_VALIDATE = Disclosures() -DISCLOSURES_EXAMPLE = Disclosures().EXAMPLE - DID_KEY_VALIDATE = DIDKey() DID_KEY_EXAMPLE = DIDKey.EXAMPLE diff --git a/aries_cloudagent/wallet/routes.py b/aries_cloudagent/wallet/routes.py index d7ba16a999..458bbc55c9 100644 --- a/aries_cloudagent/wallet/routes.py +++ b/aries_cloudagent/wallet/routes.py @@ -23,8 +23,6 @@ from ..messaging.valid import ( DID_POSTURE_EXAMPLE, DID_POSTURE_VALIDATE, - DISCLOSURES_EXAMPLE, - DISCLOSURES_VALIDATE, ENDPOINT_EXAMPLE, ENDPOINT_TYPE_EXAMPLE, ENDPOINT_TYPE_VALIDATE, @@ -219,15 +217,7 @@ class JWSVerifyResponseSchema(OpenAPISchema): class SDJWSVerifyResponseSchema(JWSVerifyResponseSchema): """Response schema for SD-JWT verification result.""" - disclosures = fields.List( - fields.List( - fields.Str( - required=False, - validate=DISCLOSURES_VALIDATE, - metadata={"example": DISCLOSURES_EXAMPLE}, - ) - ) - ) + disclosures = fields.List(fields.List(fields.Str())) # TODO: check class DIDEndpointSchema(OpenAPISchema): From f7d5c8d317b0c10943bda019574920179487f09f Mon Sep 17 00:00:00 2001 From: Char Howland Date: Mon, 21 Aug 2023 15:36:59 -0600 Subject: [PATCH 21/85] fix: add disclosures to sd-jwt verify json response Signed-off-by: Char Howland --- aries_cloudagent/wallet/routes.py | 5 ++++- aries_cloudagent/wallet/sd_jwt.py | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/aries_cloudagent/wallet/routes.py b/aries_cloudagent/wallet/routes.py index 458bbc55c9..878ae39af5 100644 --- a/aries_cloudagent/wallet/routes.py +++ b/aries_cloudagent/wallet/routes.py @@ -1037,7 +1037,9 @@ async def wallet_jwt_verify(request: web.BaseRequest): ) -@docs(tags=["wallet"], summary="Verify a EdDSA sd-jws using did keys with a given JWS") +@docs( + tags=["wallet"], summary="Verify a EdDSA sd-jws using did keys with a given SD-JWS" +) @request_schema(SDJWSVerifySchema()) @response_schema(SDJWSVerifyResponseSchema(), 200, description="") async def wallet_sd_jwt_verify(request: web.BaseRequest): @@ -1063,6 +1065,7 @@ async def wallet_sd_jwt_verify(request: web.BaseRequest): "headers": result.headers, "payload": result.payload, "kid": result.kid, + "disclosures": result.disclosures, } ) diff --git a/aries_cloudagent/wallet/sd_jwt.py b/aries_cloudagent/wallet/sd_jwt.py index fbef87d953..05c9ebc9cb 100644 --- a/aries_cloudagent/wallet/sd_jwt.py +++ b/aries_cloudagent/wallet/sd_jwt.py @@ -95,7 +95,7 @@ async def sd_jwt_sign( jsonpath_expression = parse(f"$.{sd}") matches = jsonpath_expression.find(payload) if len(matches) < 1: - raise SDJWTError("Claim for {sd} not found in payload.") + raise SDJWTError(f"Claim for {sd} not found in payload.") else: for match in matches: if type(match.context.value) is list: From 5daf780208a5d22e1f6fda9784d8012a8c52edff Mon Sep 17 00:00:00 2001 From: Char Howland Date: Thu, 24 Aug 2023 12:28:21 -0600 Subject: [PATCH 22/85] fix: validation for SDJWSVerifyResponseSchema Signed-off-by: Char Howland --- aries_cloudagent/messaging/valid.py | 2 +- aries_cloudagent/wallet/routes.py | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/aries_cloudagent/messaging/valid.py b/aries_cloudagent/messaging/valid.py index 7c7178a49c..d1bc31c3c1 100644 --- a/aries_cloudagent/messaging/valid.py +++ b/aries_cloudagent/messaging/valid.py @@ -209,7 +209,7 @@ class SDList(Regexp): "address.street_address", "nationalities[1:3]", ] - PATTERN = r"[a-z0-9:\[\]_\.@?\(\)]" # TODO: check + PATTERN = r"[a-z0-9:\[\]_\.@?\(\)]" def __init__(self): """Initialize the instance.""" diff --git a/aries_cloudagent/wallet/routes.py b/aries_cloudagent/wallet/routes.py index 878ae39af5..c53da9a31c 100644 --- a/aries_cloudagent/wallet/routes.py +++ b/aries_cloudagent/wallet/routes.py @@ -40,6 +40,7 @@ SD_LIST_EXAMPLE, SD_LIST_VALIDATE, IndyDID, + StrOrDictField, Uri, ) from ..protocols.coordinate_mediation.v1_0.route_manager import RouteManager @@ -217,7 +218,20 @@ class JWSVerifyResponseSchema(OpenAPISchema): class SDJWSVerifyResponseSchema(JWSVerifyResponseSchema): """Response schema for SD-JWT verification result.""" - disclosures = fields.List(fields.List(fields.Str())) # TODO: check + disclosures = fields.List( + fields.List(StrOrDictField()), + metadata={ + "description": "Disclosure arrays associated with the SD-JWT", + "example": [ + ["fx1iT_mETjGiC-JzRARnVg", "name", "Alice"], + [ + "n4-t3mlh8jSS6yMIT7QHnA", + "street_address", + {"_sd": ["kLZrLK7enwfqeOzJ9-Ss88YS3mhjOAEk9lr_ix2Heng"]}, + ], + ], + }, + ) class DIDEndpointSchema(OpenAPISchema): From a702859ffdfd91a687dbf757ae8b0182a79b4b0a Mon Sep 17 00:00:00 2001 From: Char Howland Date: Thu, 24 Aug 2023 12:29:51 -0600 Subject: [PATCH 23/85] fix: use inheritance for SDJWTVerifyResult Signed-off-by: Char Howland --- aries_cloudagent/wallet/sd_jwt.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/aries_cloudagent/wallet/sd_jwt.py b/aries_cloudagent/wallet/sd_jwt.py index 05c9ebc9cb..aaee7cb7e9 100644 --- a/aries_cloudagent/wallet/sd_jwt.py +++ b/aries_cloudagent/wallet/sd_jwt.py @@ -1,14 +1,14 @@ """Operations supporting SD-JWT creation and verification.""" import json -from typing import Any, List, Mapping, NamedTuple, Optional +from typing import Any, List, Mapping, Optional from jsonpath_ng.ext import parse from sd_jwt.common import SDObj from sd_jwt.issuer import SDJWTIssuer from sd_jwt.verifier import SDJWTVerifier from ..core.profile import Profile -from ..wallet.jwt import jwt_sign, jwt_verify +from ..wallet.jwt import JWTVerifyResult, jwt_sign, jwt_verify from ..core.error import BaseError @@ -120,20 +120,17 @@ async def sd_jwt_sign( verification_method=verification_method, ) await sd_jwt_issuer.issue() - print(json.dumps(sd_jwt_issuer.sd_jwt_payload, indent=4)) return sd_jwt_issuer.sd_jwt_issuance -class SDJWTVerifyResult(NamedTuple): +class SDJWTVerifyResult(JWTVerifyResult): """Result from verifying SD-JWT""" - headers: Mapping[str, Any] - payload: Mapping[str, Any] - valid: bool - kid: str - disclosures: str - # TODO: figure out inheritance + disclosures: list + + def add_disclosures(self, disclosures): + self.disclosures = disclosures class SDJWTVerifierACAPy(SDJWTVerifier): @@ -155,13 +152,14 @@ async def _verify_sd_jwt(self) -> SDJWTVerifyResult: self.profile, self.serialized_sd_jwt, ) - return SDJWTVerifyResult( + sd_jwt_verify_result = SDJWTVerifyResult( headers=verified.headers, payload=verified.payload, valid=verified.valid, kid=verified.kid, - disclosures=self._disclosures_list, ) + sd_jwt_verify_result.add_disclosures(self._disclosures_list) + return sd_jwt_verify_result def _parse_sd_jwt(self, sd_jwt): if self._serialization_format == "compact": From 2f6f49d86a1b9b0dd460ea2c34f9127c614c3e7a Mon Sep 17 00:00:00 2001 From: Char Howland Date: Thu, 24 Aug 2023 12:30:29 -0600 Subject: [PATCH 24/85] fix: remove unnecessary type checking Signed-off-by: Char Howland --- aries_cloudagent/wallet/sd_jwt.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/aries_cloudagent/wallet/sd_jwt.py b/aries_cloudagent/wallet/sd_jwt.py index aaee7cb7e9..b284ecc44f 100644 --- a/aries_cloudagent/wallet/sd_jwt.py +++ b/aries_cloudagent/wallet/sd_jwt.py @@ -101,14 +101,10 @@ async def sd_jwt_sign( if type(match.context.value) is list: match.context.value.remove(match.value) match.context.value.append(SDObj(match.value)) - elif type(match.context.value) is str or int or dict or bool: + else: match.context.value[ SDObj(str(match.path)) ] = match.context.value.pop(str(match.path)) - else: - raise SDJWTError( - f"Unrecognized type {type(match.context.value)} for {match.path}" - ) sd_jwt_issuer = SDJWTIssuerACAPy( user_claims=payload, From 66558a42a9976584f10342206d26a2a80ae6e46d Mon Sep 17 00:00:00 2001 From: Char Howland Date: Thu, 24 Aug 2023 12:31:01 -0600 Subject: [PATCH 25/85] feat: add else for JWS JSON serialization Signed-off-by: Char Howland --- aries_cloudagent/wallet/sd_jwt.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/aries_cloudagent/wallet/sd_jwt.py b/aries_cloudagent/wallet/sd_jwt.py index b284ecc44f..3a5a2ac4ff 100644 --- a/aries_cloudagent/wallet/sd_jwt.py +++ b/aries_cloudagent/wallet/sd_jwt.py @@ -164,8 +164,18 @@ def _parse_sd_jwt(self, sd_jwt): *self._input_disclosures, self._unverified_input_key_binding_jwt, ) = self._split(sd_jwt) - return self._unverified_input_sd_jwt - # TOOD: what to do about the else? + else: + # if the SD-JWT is in JSON format, parse the json and extract the disclosures. + self._unverified_input_sd_jwt = sd_jwt + self._unverified_input_sd_jwt_parsed = json.loads(sd_jwt) + self._input_disclosures = self._unverified_input_sd_jwt_parsed[ + self.JWS_KEY_DISCLOSURES + ] + self._unverified_input_key_binding_jwt = ( + self._unverified_input_sd_jwt_parsed.get(self.JWS_KEY_KB_JWT, "") + ) + + return self._unverified_input_sd_jwt def _create_disclosures_list(self) -> List: disclosures_list = [] From 30c6e5c1957343fe96ed1f53ab5496833e31fb34 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Thu, 24 Aug 2023 12:34:40 -0600 Subject: [PATCH 26/85] test: fixture for payload Signed-off-by: Char Howland --- aries_cloudagent/wallet/tests/test_sd_jwt.py | 67 ++++++++++---------- 1 file changed, 32 insertions(+), 35 deletions(-) diff --git a/aries_cloudagent/wallet/tests/test_sd_jwt.py b/aries_cloudagent/wallet/tests/test_sd_jwt.py index 64e4f858b2..550984e371 100644 --- a/aries_cloudagent/wallet/tests/test_sd_jwt.py +++ b/aries_cloudagent/wallet/tests/test_sd_jwt.py @@ -1,53 +1,35 @@ -from unittest import TestCase import pytest -from aries_cloudagent.resolver.did_resolver import DIDResolver -from aries_cloudagent.resolver.tests.test_did_resolver import MockResolver -from aries_cloudagent.wallet.default_verification_key_strategy import ( - BaseVerificationKeyStrategy, - DefaultVerificationKeyStrategy, -) -from jsonpath_ng.ext import parse -import json - -from ...core.in_memory.profile import InMemoryProfile -from ...wallet.did_method import KEY, DIDMethods +from ...wallet.did_method import KEY from ...wallet.key_type import ED25519 -from ...wallet.in_memory import InMemoryWallet -from ..jwt import jwt_sign, jwt_verify, resolve_public_key_by_kid_for_verify -from ..sd_jwt import sd_jwt_sign, sd_jwt_verify +from ..sd_jwt import SDJWTVerifyResult, sd_jwt_sign, sd_jwt_verify from .test_jwt import profile, in_memory_wallet +@pytest.fixture +def create_address_payload(): + return { + "address": { + "street_address": "123 Main St", + "locality": "Anytown", + "region": "Anystate", + "country": "US", + } + } + + class TestSDJWT: """Tests for JWT sign and verify using dids.""" seed = "testseed000000000000000000000001" + headers = {} @pytest.mark.asyncio async def test_sign_with_did_key_and_verify(self, profile, in_memory_wallet): did_info = await in_memory_wallet.create_local_did(KEY, ED25519, self.seed) - did = did_info.did verification_method = None - sd_list = [ - "address", - "address.street_address", - "address.street_address.house_number", - "address.locality", - "address.region", - "address.country", - "given_name", - "family_name", - "email", - "phone_number", - "phone_number_verified", - "birthdate", - "updated_at", - "nationalities[1:3]", - ] - headers = {} payload = { "sub": "user_42", "given_name": "John", @@ -68,10 +50,25 @@ async def test_sign_with_did_key_and_verify(self, profile, in_memory_wallet): "updated_at": 1570000000, "nationalities": ["US", "DE", "SA"], } + sd_list = [ + "address", + "address.street_address", + "address.street_address.house_number", + "address.locality", + "address.region", + "address.country", + "given_name", + "family_name", + "email", + "phone_number", + "phone_number_verified", + "birthdate", + "updated_at", + "nationalities[1:3]", + ] signed = await sd_jwt_sign( - profile, headers, payload, sd_list, did, verification_method + profile, self.headers, payload, sd_list, did_info.did, verification_method ) - assert signed assert await sd_jwt_verify(profile, signed) From 786d1078ecf1ea112f7ff81b9ae1154ac87e790e Mon Sep 17 00:00:00 2001 From: Char Howland Date: Thu, 24 Aug 2023 12:35:17 -0600 Subject: [PATCH 27/85] test: payloads with nested structures and array elements Signed-off-by: Char Howland --- aries_cloudagent/wallet/tests/test_sd_jwt.py | 120 +++++++++++++++++++ 1 file changed, 120 insertions(+) diff --git a/aries_cloudagent/wallet/tests/test_sd_jwt.py b/aries_cloudagent/wallet/tests/test_sd_jwt.py index 550984e371..835365f388 100644 --- a/aries_cloudagent/wallet/tests/test_sd_jwt.py +++ b/aries_cloudagent/wallet/tests/test_sd_jwt.py @@ -72,3 +72,123 @@ async def test_sign_with_did_key_and_verify(self, profile, in_memory_wallet): assert signed assert await sd_jwt_verify(profile, signed) + + @pytest.mark.asyncio + async def test_flat_structure( + self, profile, in_memory_wallet, create_address_payload + ): + did_info = await in_memory_wallet.create_local_did(KEY, ED25519, self.seed) + verification_method = None + sd_list = ["address"] + signed = await sd_jwt_sign( + profile, + self.headers, + create_address_payload, + sd_list, + did_info.did, + verification_method, + ) + assert signed + + verified = await sd_jwt_verify(profile, signed) + assert isinstance(verified, SDJWTVerifyResult) + assert verified.valid + assert verified.payload["_sd"] + assert len(verified.payload["_sd"]) >= len(sd_list) + assert verified.payload["_sd_alg"] + for disclosure in verified.disclosures: + assert disclosure[1] in sd_list + + @pytest.mark.asyncio + async def test_nested_structure( + self, profile, in_memory_wallet, create_address_payload + ): + did_info = await in_memory_wallet.create_local_did(KEY, ED25519, self.seed) + verification_method = None + sd_list = [ + "address.street_address", + "address.locality", + "address.region", + "address.country", + ] + + signed = await sd_jwt_sign( + profile, + self.headers, + create_address_payload, + sd_list, + did_info.did, + verification_method, + ) + assert signed + + verified = await sd_jwt_verify(profile, signed) + assert isinstance(verified, SDJWTVerifyResult) + assert verified.valid + assert len(verified.payload["address"]["_sd"]) >= len(sd_list) + assert verified.payload["_sd_alg"] + for disclosure in verified.disclosures: + assert f"address.{disclosure[1]}" in sd_list + + @pytest.mark.asyncio + async def test_recursive_nested_structure( + self, profile, in_memory_wallet, create_address_payload + ): + did_info = await in_memory_wallet.create_local_did(KEY, ED25519, self.seed) + verification_method = None + sd_list = [ + "address", + "address.street_address", + "address.locality", + "address.region", + "address.country", + ] + + signed = await sd_jwt_sign( + profile, + self.headers, + create_address_payload, + sd_list, + did_info.did, + verification_method, + ) + assert signed + + verified = await sd_jwt_verify(profile, signed) + assert isinstance(verified, SDJWTVerifyResult) + assert verified.valid + assert "address" not in verified + assert verified.payload["_sd"] + assert verified.payload["_sd_alg"] + for disclosure in verified.disclosures: + if disclosure[1] == "address": + assert isinstance(disclosure[2], dict) + assert len(disclosure[2]["_sd"]) >= len(sd_list) - 1 + else: + assert f"address.{disclosure[1]}" in sd_list + + @pytest.mark.asyncio + async def test_list_splice(self, profile, in_memory_wallet): + did_info = await in_memory_wallet.create_local_did(KEY, ED25519, self.seed) + payload = {"nationalities": ["US", "DE", "SA"]} + verification_method = None + sd_list = ["nationalities[1:3]"] + + signed = await sd_jwt_sign( + profile, self.headers, payload, sd_list, did_info.did, verification_method + ) + assert signed + + verified = await sd_jwt_verify(profile, signed) + assert isinstance(verified, SDJWTVerifyResult) + assert verified.valid + for nationality in verified.payload["nationalities"]: + if isinstance(nationality, dict): + assert nationality["..."] + assert len(nationality) == 1 + else: + assert nationality in payload["nationalities"] + assert verified.payload["_sd_alg"] + spliced = [element.value for element in payload["nationalities"][1:3]] + for disclosure in verified.disclosures: + assert disclosure[1] in spliced From c0608baf32e8f8c2a8c8dd4904f670177d27049e Mon Sep 17 00:00:00 2001 From: Char Howland Date: Thu, 24 Aug 2023 15:04:35 -0600 Subject: [PATCH 28/85] feat: use BaseModel for JWTVerifyResult/SDJWTVerifyResult Signed-off-by: Char Howland --- aries_cloudagent/wallet/jwt.py | 45 ++++++++++++++--- aries_cloudagent/wallet/routes.py | 10 +--- aries_cloudagent/wallet/sd_jwt.py | 53 +++++++++++++++++--- aries_cloudagent/wallet/tests/test_sd_jwt.py | 2 +- 4 files changed, 88 insertions(+), 22 deletions(-) diff --git a/aries_cloudagent/wallet/jwt.py b/aries_cloudagent/wallet/jwt.py index fa9580e856..34d3eb65d5 100644 --- a/aries_cloudagent/wallet/jwt.py +++ b/aries_cloudagent/wallet/jwt.py @@ -2,13 +2,15 @@ import json import logging -from typing import Any, Mapping, NamedTuple, Optional +from typing import Any, Mapping, Optional +from marshmallow import fields from pydid import DIDUrl, Resource, VerificationMethod from ..core.profile import Profile from ..messaging.jsonld.error import BadJWSHeaderError, InvalidVerificationMethod from ..messaging.jsonld.routes import SUPPORTED_VERIFICATION_METHOD_TYPES +from ..messaging.models.base import BaseModel, BaseModelSchema from ..resolver.did_resolver import DIDResolver from .default_verification_key_strategy import BaseVerificationKeyStrategy from .base import BaseWallet @@ -88,13 +90,44 @@ async def jwt_sign( return f"{encoded_headers}.{encoded_payload}.{sig}" -class JWTVerifyResult(NamedTuple): +class JWTVerifyResult(BaseModel): """Result from verify.""" - headers: Mapping[str, Any] - payload: Mapping[str, Any] - valid: bool - kid: str + class Meta: + """JWTVerifyResult metadata.""" + + schema_class = "JWTVerifyResultSchema" + + def __init__( + self, + headers: Mapping[str, Any], + payload: Mapping[str, Any], + valid: bool, + kid: str, + ): + self.headers = headers + self.payload = payload + self.valid = valid + self.kid = kid + + +class JWTVerifyResultSchema(BaseModelSchema): + """JWTVerifyResult schema""" + + class Meta: + """JWTVerifyResultSchema metadata.""" + + model_class = JWTVerifyResult + + headers = fields.Dict( + required=True, metadata={"description": "Headers from verified JWT."} + ) + payload = fields.Dict( + required=True, metadata={"description": "Payload from verified JWT"} + ) + valid = fields.Bool(required=True) + kid = fields.Str(required=True, metadata={"description": "kid of signer"}) + error = fields.Str(required=False, metadata={"description": "Error text"}) async def resolve_public_key_by_kid_for_verify(profile: Profile, kid: str) -> str: diff --git a/aries_cloudagent/wallet/routes.py b/aries_cloudagent/wallet/routes.py index c53da9a31c..a8d1b81b6c 100644 --- a/aries_cloudagent/wallet/routes.py +++ b/aries_cloudagent/wallet/routes.py @@ -1073,15 +1073,7 @@ async def wallet_sd_jwt_verify(request: web.BaseRequest): except ResolverError as err: raise web.HTTPNotFound(reason=err.roll_up) from err - return web.json_response( - { - "valid": result.valid, - "headers": result.headers, - "payload": result.payload, - "kid": result.kid, - "disclosures": result.disclosures, - } - ) + return web.json_response(result.serialize()) @docs(tags=["wallet"], summary="Query DID endpoint in wallet") diff --git a/aries_cloudagent/wallet/sd_jwt.py b/aries_cloudagent/wallet/sd_jwt.py index 3a5a2ac4ff..b265df849d 100644 --- a/aries_cloudagent/wallet/sd_jwt.py +++ b/aries_cloudagent/wallet/sd_jwt.py @@ -2,14 +2,16 @@ import json from typing import Any, List, Mapping, Optional +from marshmallow import fields from jsonpath_ng.ext import parse from sd_jwt.common import SDObj from sd_jwt.issuer import SDJWTIssuer from sd_jwt.verifier import SDJWTVerifier from ..core.profile import Profile -from ..wallet.jwt import JWTVerifyResult, jwt_sign, jwt_verify +from ..wallet.jwt import JWTVerifyResult, JWTVerifyResultSchema, jwt_sign, jwt_verify from ..core.error import BaseError +from ..messaging.valid import StrOrDictField class SDJWTError(BaseError): @@ -123,12 +125,52 @@ async def sd_jwt_sign( class SDJWTVerifyResult(JWTVerifyResult): """Result from verifying SD-JWT""" - disclosures: list + class Meta: + """SDJWTVerifyResult metadata.""" - def add_disclosures(self, disclosures): + schema_class = "SDJWTVerifyResultSchema" + + def __init__( + self, + headers, + payload, + valid, + kid, + disclosures, + ): + super().__init__( + headers, + payload, + valid, + kid, + ) self.disclosures = disclosures +class SDJWTVerifyResultSchema(JWTVerifyResultSchema): + """SDJWTVerifyResult schema""" + + class Meta: + """SDJWTVerifyResultSchema metadata.""" + + model_class = SDJWTVerifyResult + + disclosures = fields.List( + fields.List(StrOrDictField()), + metadata={ + "description": "Disclosure arrays associated with the SD-JWT", + "example": [ + ["fx1iT_mETjGiC-JzRARnVg", "name", "Alice"], + [ + "n4-t3mlh8jSS6yMIT7QHnA", + "street_address", + {"_sd": ["kLZrLK7enwfqeOzJ9-Ss88YS3mhjOAEk9lr_ix2Heng"]}, + ], + ], + }, + ) + + class SDJWTVerifierACAPy(SDJWTVerifier): """SDJWTVerifier class for ACA-Py implementation.""" @@ -148,14 +190,13 @@ async def _verify_sd_jwt(self) -> SDJWTVerifyResult: self.profile, self.serialized_sd_jwt, ) - sd_jwt_verify_result = SDJWTVerifyResult( + return SDJWTVerifyResult( headers=verified.headers, payload=verified.payload, valid=verified.valid, kid=verified.kid, + disclosures=self._disclosures_list, ) - sd_jwt_verify_result.add_disclosures(self._disclosures_list) - return sd_jwt_verify_result def _parse_sd_jwt(self, sd_jwt): if self._serialization_format == "compact": diff --git a/aries_cloudagent/wallet/tests/test_sd_jwt.py b/aries_cloudagent/wallet/tests/test_sd_jwt.py index 835365f388..81a7daa5b6 100644 --- a/aries_cloudagent/wallet/tests/test_sd_jwt.py +++ b/aries_cloudagent/wallet/tests/test_sd_jwt.py @@ -157,7 +157,7 @@ async def test_recursive_nested_structure( verified = await sd_jwt_verify(profile, signed) assert isinstance(verified, SDJWTVerifyResult) assert verified.valid - assert "address" not in verified + assert "address" not in verified.payload assert verified.payload["_sd"] assert verified.payload["_sd_alg"] for disclosure in verified.disclosures: From d41278810a8975e4c99afa72303a88e602247644 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Fri, 25 Aug 2023 09:35:29 -0600 Subject: [PATCH 29/85] fix: exponential backtracking issue Signed-off-by: Char Howland --- aries_cloudagent/messaging/valid.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aries_cloudagent/messaging/valid.py b/aries_cloudagent/messaging/valid.py index d1bc31c3c1..bdf1b26fd0 100644 --- a/aries_cloudagent/messaging/valid.py +++ b/aries_cloudagent/messaging/valid.py @@ -250,7 +250,7 @@ class SDJSONWebToken(Regexp): "~WyJPMTFySVRjRTdHcXExYW9oRkd0aDh3IiwgIlNBIl0" "~WyJkVmEzX1JlTGNsWTU0R1FHZm5oWlRnIiwgInVwZGF0ZWRfYXQiLCAxNTcwMDAwMDAwXQ" ) - PATTERN = r"^[-_a-zA-Z0-9]*\.[-_a-zA-Z0-9]*\.[-_a-zA-Z0-9]*([~Wy][a-zA-Z0-9]*)*$" + PATTERN = r"^[-_a-zA-Z0-9]*\.[-_a-zA-Z0-9]*\.[-_a-zA-Z0-9]*(~Wy[a-zA-Z0-9]*)*$" def __init__(self): """Initialize the instance.""" From a46c4fe7c483872a5e0c41dc8ead2013bf7bf69c Mon Sep 17 00:00:00 2001 From: Char Howland Date: Fri, 25 Aug 2023 09:37:14 -0600 Subject: [PATCH 30/85] fix: regex for SDJSONWebToken Signed-off-by: Char Howland --- aries_cloudagent/messaging/valid.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aries_cloudagent/messaging/valid.py b/aries_cloudagent/messaging/valid.py index bdf1b26fd0..f51d83e596 100644 --- a/aries_cloudagent/messaging/valid.py +++ b/aries_cloudagent/messaging/valid.py @@ -250,7 +250,7 @@ class SDJSONWebToken(Regexp): "~WyJPMTFySVRjRTdHcXExYW9oRkd0aDh3IiwgIlNBIl0" "~WyJkVmEzX1JlTGNsWTU0R1FHZm5oWlRnIiwgInVwZGF0ZWRfYXQiLCAxNTcwMDAwMDAwXQ" ) - PATTERN = r"^[-_a-zA-Z0-9]*\.[-_a-zA-Z0-9]*\.[-_a-zA-Z0-9]*(~Wy[a-zA-Z0-9]*)*$" + PATTERN = r"^[-_a-zA-Z0-9]*\.[-_a-zA-Z0-9]*\.[-_a-zA-Z0-9]*(~Wy[a-zA-Z0-9]+)*$" def __init__(self): """Initialize the instance.""" From 14a6df632b8b947271833bc14edc579ddbf5c312 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Fri, 25 Aug 2023 14:46:47 -0600 Subject: [PATCH 31/85] fix: add ~ to regex Signed-off-by: Char Howland --- aries_cloudagent/messaging/valid.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aries_cloudagent/messaging/valid.py b/aries_cloudagent/messaging/valid.py index f51d83e596..7b6088ae47 100644 --- a/aries_cloudagent/messaging/valid.py +++ b/aries_cloudagent/messaging/valid.py @@ -250,7 +250,7 @@ class SDJSONWebToken(Regexp): "~WyJPMTFySVRjRTdHcXExYW9oRkd0aDh3IiwgIlNBIl0" "~WyJkVmEzX1JlTGNsWTU0R1FHZm5oWlRnIiwgInVwZGF0ZWRfYXQiLCAxNTcwMDAwMDAwXQ" ) - PATTERN = r"^[-_a-zA-Z0-9]*\.[-_a-zA-Z0-9]*\.[-_a-zA-Z0-9]*(~Wy[a-zA-Z0-9]+)*$" + PATTERN = r"^[-_a-zA-Z0-9]*\.[-_a-zA-Z0-9]*\.[-_a-zA-Z0-9]*(~Wy[~a-zA-Z0-9]+)*$" def __init__(self): """Initialize the instance.""" From 28fcd42e1bbd4a459595fa5bc4449c22404dac8d Mon Sep 17 00:00:00 2001 From: Char Howland Date: Fri, 25 Aug 2023 14:51:10 -0600 Subject: [PATCH 32/85] feat: invert sd list validation and examples Signed-off-by: Char Howland --- aries_cloudagent/messaging/valid.py | 12 ++++++------ aries_cloudagent/wallet/routes.py | 15 ++++++++------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/aries_cloudagent/messaging/valid.py b/aries_cloudagent/messaging/valid.py index 7b6088ae47..b9fb578954 100644 --- a/aries_cloudagent/messaging/valid.py +++ b/aries_cloudagent/messaging/valid.py @@ -200,8 +200,8 @@ def __init__(self): ) -class SDList(Regexp): - """Validate SD List""" +class NonSDList(Regexp): + """Validate NonSD List""" EXAMPLE = [ "name", @@ -215,8 +215,8 @@ def __init__(self): """Initialize the instance.""" super().__init__( - SDList.PATTERN, - error="Value {input} is not a valid SDList", + NonSDList.PATTERN, + error="Value {input} is not a valid NonSDList", ) @@ -842,8 +842,8 @@ def __init__( JWS_HEADER_KID_VALIDATE = JWSHeaderKid() JWS_HEADER_KID_EXAMPLE = JWSHeaderKid.EXAMPLE -SD_LIST_VALIDATE = SDList() -SD_LIST_EXAMPLE = SDList().EXAMPLE +NON_SD_LIST_VALIDATE = NonSDList() +NON_SD_LIST_EXAMPLE = NonSDList().EXAMPLE JWT_VALIDATE = JSONWebToken() JWT_EXAMPLE = JSONWebToken.EXAMPLE diff --git a/aries_cloudagent/wallet/routes.py b/aries_cloudagent/wallet/routes.py index a8d1b81b6c..3ae9b16e5c 100644 --- a/aries_cloudagent/wallet/routes.py +++ b/aries_cloudagent/wallet/routes.py @@ -37,8 +37,8 @@ JWT_VALIDATE, SD_JWT_EXAMPLE, SD_JWT_VALIDATE, - SD_LIST_EXAMPLE, - SD_LIST_VALIDATE, + NON_SD_LIST_EXAMPLE, + NON_SD_LIST_VALIDATE, IndyDID, StrOrDictField, Uri, @@ -180,11 +180,11 @@ class JWSCreateSchema(OpenAPISchema): class SDJWSCreateSchema(JWSCreateSchema): """Request schema to create an sd-jws with a particular DID.""" - sd_list = fields.List( + non_sd_list = fields.List( fields.Str( required=False, - validate=SD_LIST_VALIDATE, - metadata={"example": SD_LIST_EXAMPLE}, + validate=NON_SD_LIST_VALIDATE, + metadata={"example": NON_SD_LIST_EXAMPLE}, ) ) @@ -999,6 +999,7 @@ async def wallet_sd_jwt_sign(request: web.BaseRequest): "did": "did:example:123", "verificationMethod": "did:example:123#keys-1" with did and verification being mutually exclusive. + "non_sd_list": [] """ context: AdminRequestContext = request["context"] body = await request.json() @@ -1006,11 +1007,11 @@ async def wallet_sd_jwt_sign(request: web.BaseRequest): verification_method = body.get("verificationMethod") headers = body.get("headers", {}) payload = body.get("payload", {}) - sd_list = body.get("sd_list", []) + non_sd_list = body.get("non_sd_list", []) try: sd_jws = await sd_jwt_sign( - context.profile, headers, payload, sd_list, did, verification_method + context.profile, headers, payload, non_sd_list, did, verification_method ) except ValueError as err: raise web.HTTPBadRequest(reason="Bad did or verification method") from err From f721cc35b9c00a95faa38b9f33d69216b6489bcd Mon Sep 17 00:00:00 2001 From: Char Howland Date: Fri, 25 Aug 2023 14:53:13 -0600 Subject: [PATCH 33/85] feat: method to create json paths from payload Signed-off-by: Char Howland --- aries_cloudagent/wallet/sd_jwt.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/aries_cloudagent/wallet/sd_jwt.py b/aries_cloudagent/wallet/sd_jwt.py index b265df849d..8a37ab89c7 100644 --- a/aries_cloudagent/wallet/sd_jwt.py +++ b/aries_cloudagent/wallet/sd_jwt.py @@ -64,6 +64,36 @@ async def issue(self): self._create_combined() +def create_json_paths(it, current_path="", path_list=None): + """ + Create a json path for each element of the payload. + """ + if path_list is None: + path_list = [] + + if type(it) is dict: + for k, v in it.items(): + new_key = f"{current_path}.{k}" if current_path else k + path_list.append(new_key) + + if isinstance(v, dict): + create_json_paths(v, new_key, path_list) + elif isinstance(v, list): + for i, e in enumerate(v): + if isinstance(e, (dict, list)): + create_json_paths(e, f"{new_key}[{i}]", path_list) + else: + path_list.append(f"{new_key}[{i}]") + elif type(it) is list: + for i, e in enumerate(it): + if isinstance(e, (dict, list)): + create_json_paths(e, f"{current_path}[{i}]", path_list) + else: + path_list.append(f"{current_path}[{i}]") + + return path_list + + def sort_sd_list(sd_list): """ Sorts sd_list. From 514852009608efd9425457ee93b7f58574dacfbe Mon Sep 17 00:00:00 2001 From: Char Howland Date: Fri, 25 Aug 2023 14:55:55 -0600 Subject: [PATCH 34/85] feat: method to handle list splices Signed-off-by: Char Howland --- aries_cloudagent/wallet/sd_jwt.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/aries_cloudagent/wallet/sd_jwt.py b/aries_cloudagent/wallet/sd_jwt.py index 8a37ab89c7..52b141eb1f 100644 --- a/aries_cloudagent/wallet/sd_jwt.py +++ b/aries_cloudagent/wallet/sd_jwt.py @@ -1,6 +1,7 @@ """Operations supporting SD-JWT creation and verification.""" import json +import re from typing import Any, List, Mapping, Optional from marshmallow import fields from jsonpath_ng.ext import parse @@ -106,6 +107,24 @@ def sort_sd_list(sd_list): return [sd[1] for sd in nested_claim_sort] +def separate_list_splices(non_sd_list): + """ + Separate list splices in the non_sd_list into individual indices. + + This is necessary in order to properly construct the inverse of + the claims which should not be selectively disclosable in the case + of list splices. + """ + for item in non_sd_list: + if ":" in item: + split = re.split(r"\[|\]|:", item) + for i in range(int(split[1]), int(split[2])): + non_sd_list.append(f"{split[0]}[{i}]") + non_sd_list.remove(item) + + return non_sd_list + + async def sd_jwt_sign( profile: Profile, headers: Mapping[str, Any], From aa28ba12190a1995e7f1b9bb7a6ca4e6aef6adbf Mon Sep 17 00:00:00 2001 From: Char Howland Date: Fri, 25 Aug 2023 14:58:48 -0600 Subject: [PATCH 35/85] feat: invert sd handling such that sd is default Signed-off-by: Char Howland --- aries_cloudagent/wallet/sd_jwt.py | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/aries_cloudagent/wallet/sd_jwt.py b/aries_cloudagent/wallet/sd_jwt.py index 52b141eb1f..8f6b48920b 100644 --- a/aries_cloudagent/wallet/sd_jwt.py +++ b/aries_cloudagent/wallet/sd_jwt.py @@ -125,24 +125,36 @@ def separate_list_splices(non_sd_list): return non_sd_list +def create_sd_list(payload, non_sd_list): + """ + Create a list of claims which will be selectively disclosable. + """ + flattened_payload = create_json_paths(payload) + separated_non_sd_list = separate_list_splices(non_sd_list) + sd_list = [ + claim for claim in flattened_payload if claim not in separated_non_sd_list + ] + return sort_sd_list(sd_list) + + async def sd_jwt_sign( profile: Profile, headers: Mapping[str, Any], payload: Mapping[str, Any], - sd_list: List, + non_sd_list: List, did: Optional[str] = None, verification_method: Optional[str] = None, ) -> str: """ Sign sd-jwt. - Use sd_list to wrap selectively disclosable claims with - SDObj within payload, create SDJWTIssuerACAPy object, and - call SDJWTIssuerACAPy.issue(). + Use non_sd_list and json paths for payload elements to create a list of + claims that can be selectively disclosable. Use this list to wrap + selectively disclosable claims with SDObj within payload, + create SDJWTIssuerACAPy object, and call SDJWTIssuerACAPy.issue(). """ - - sorted_sd_list = sort_sd_list(sd_list) - for sd in sorted_sd_list: + sd_list = create_sd_list(payload, non_sd_list) + for sd in sd_list: jsonpath_expression = parse(f"$.{sd}") matches = jsonpath_expression.find(payload) if len(matches) < 1: From f0d0644b5b6fcad64dca67d652ce5d75205eeec4 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Fri, 25 Aug 2023 14:59:24 -0600 Subject: [PATCH 36/85] feat: add list of claims which are always visible Signed-off-by: Char Howland --- aries_cloudagent/wallet/sd_jwt.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/aries_cloudagent/wallet/sd_jwt.py b/aries_cloudagent/wallet/sd_jwt.py index 8f6b48920b..e5fb80e037 100644 --- a/aries_cloudagent/wallet/sd_jwt.py +++ b/aries_cloudagent/wallet/sd_jwt.py @@ -15,6 +15,9 @@ from ..messaging.valid import StrOrDictField +CLAIMS_NEVER_SD = ["iss", "iat", "exp", "cnf"] + + class SDJWTError(BaseError): """SD-JWT Error.""" @@ -161,13 +164,14 @@ async def sd_jwt_sign( raise SDJWTError(f"Claim for {sd} not found in payload.") else: for match in matches: - if type(match.context.value) is list: - match.context.value.remove(match.value) - match.context.value.append(SDObj(match.value)) - else: - match.context.value[ - SDObj(str(match.path)) - ] = match.context.value.pop(str(match.path)) + if str(match.path) not in CLAIMS_NEVER_SD: + if type(match.context.value) is list: + match.context.value.remove(match.value) + match.context.value.append(SDObj(match.value)) + else: + match.context.value[ + SDObj(str(match.path)) + ] = match.context.value.pop(str(match.path)) sd_jwt_issuer = SDJWTIssuerACAPy( user_claims=payload, From eb645cefdcccc2561a76fb2cf9c3abd310a9110e Mon Sep 17 00:00:00 2001 From: Char Howland Date: Fri, 25 Aug 2023 15:02:50 -0600 Subject: [PATCH 37/85] test: adjust tests for sd inversion Signed-off-by: Char Howland --- aries_cloudagent/wallet/tests/test_sd_jwt.py | 94 ++++++++++---------- 1 file changed, 49 insertions(+), 45 deletions(-) diff --git a/aries_cloudagent/wallet/tests/test_sd_jwt.py b/aries_cloudagent/wallet/tests/test_sd_jwt.py index 81a7daa5b6..fa97d211a4 100644 --- a/aries_cloudagent/wallet/tests/test_sd_jwt.py +++ b/aries_cloudagent/wallet/tests/test_sd_jwt.py @@ -16,7 +16,10 @@ def create_address_payload(): "locality": "Anytown", "region": "Anystate", "country": "US", - } + }, + "iss": "https://example.com/issuer", + "iat": 1683000000, + "exp": 1883000000, } @@ -49,25 +52,22 @@ async def test_sign_with_did_key_and_verify(self, profile, in_memory_wallet): "birthdate": "1940-01-01", "updated_at": 1570000000, "nationalities": ["US", "DE", "SA"], + "iss": "https://example.com/issuer", + "iat": 1683000000, + "exp": 1883000000, } - sd_list = [ - "address", - "address.street_address", - "address.street_address.house_number", - "address.locality", - "address.region", - "address.country", + non_sd_list = [ "given_name", "family_name", - "email", - "phone_number", - "phone_number_verified", - "birthdate", - "updated_at", - "nationalities[1:3]", + "nationalities", ] signed = await sd_jwt_sign( - profile, self.headers, payload, sd_list, did_info.did, verification_method + profile, + self.headers, + payload, + non_sd_list, + did_info.did, + verification_method, ) assert signed @@ -79,12 +79,19 @@ async def test_flat_structure( ): did_info = await in_memory_wallet.create_local_did(KEY, ED25519, self.seed) verification_method = None - sd_list = ["address"] + non_sd_list = [ + "address.street_address", + "address.street_address.house_number", + "address.street_address.street", + "address.locality", + "address.region", + "address.country", + ] signed = await sd_jwt_sign( profile, self.headers, create_address_payload, - sd_list, + non_sd_list, did_info.did, verification_method, ) @@ -94,10 +101,14 @@ async def test_flat_structure( assert isinstance(verified, SDJWTVerifyResult) assert verified.valid assert verified.payload["_sd"] - assert len(verified.payload["_sd"]) >= len(sd_list) assert verified.payload["_sd_alg"] - for disclosure in verified.disclosures: - assert disclosure[1] in sd_list + assert verified.disclosures[0][1] == "address" + assert verified.disclosures[0][2] == { + "street_address": "123 Main St", + "locality": "Anytown", + "region": "Anystate", + "country": "US", + } @pytest.mark.asyncio async def test_nested_structure( @@ -105,18 +116,13 @@ async def test_nested_structure( ): did_info = await in_memory_wallet.create_local_did(KEY, ED25519, self.seed) verification_method = None - sd_list = [ - "address.street_address", - "address.locality", - "address.region", - "address.country", - ] + non_sd_list = ["address"] signed = await sd_jwt_sign( profile, self.headers, create_address_payload, - sd_list, + non_sd_list, did_info.did, verification_method, ) @@ -125,10 +131,10 @@ async def test_nested_structure( verified = await sd_jwt_verify(profile, signed) assert isinstance(verified, SDJWTVerifyResult) assert verified.valid - assert len(verified.payload["address"]["_sd"]) >= len(sd_list) + assert len(verified.payload["address"]["_sd"]) >= 4 assert verified.payload["_sd_alg"] - for disclosure in verified.disclosures: - assert f"address.{disclosure[1]}" in sd_list + sd_claims = ["street_address", "region", "locality", "country"] + assert sorted(sd_claims) == sorted([claim[1] for claim in verified.disclosures]) @pytest.mark.asyncio async def test_recursive_nested_structure( @@ -136,19 +142,13 @@ async def test_recursive_nested_structure( ): did_info = await in_memory_wallet.create_local_did(KEY, ED25519, self.seed) verification_method = None - sd_list = [ - "address", - "address.street_address", - "address.locality", - "address.region", - "address.country", - ] + non_sd_list = [] signed = await sd_jwt_sign( profile, self.headers, create_address_payload, - sd_list, + non_sd_list, did_info.did, verification_method, ) @@ -160,22 +160,28 @@ async def test_recursive_nested_structure( assert "address" not in verified.payload assert verified.payload["_sd"] assert verified.payload["_sd_alg"] + sd_claims = ["street_address", "region", "locality", "country"] for disclosure in verified.disclosures: if disclosure[1] == "address": assert isinstance(disclosure[2], dict) - assert len(disclosure[2]["_sd"]) >= len(sd_list) - 1 + assert len(disclosure[2]["_sd"]) >= 4 else: - assert f"address.{disclosure[1]}" in sd_list + assert disclosure[1] in sd_claims @pytest.mark.asyncio async def test_list_splice(self, profile, in_memory_wallet): did_info = await in_memory_wallet.create_local_did(KEY, ED25519, self.seed) payload = {"nationalities": ["US", "DE", "SA"]} verification_method = None - sd_list = ["nationalities[1:3]"] + non_sd_list = ["nationalities", "nationalities[1:3]"] signed = await sd_jwt_sign( - profile, self.headers, payload, sd_list, did_info.did, verification_method + profile, + self.headers, + payload, + non_sd_list, + did_info.did, + verification_method, ) assert signed @@ -189,6 +195,4 @@ async def test_list_splice(self, profile, in_memory_wallet): else: assert nationality in payload["nationalities"] assert verified.payload["_sd_alg"] - spliced = [element.value for element in payload["nationalities"][1:3]] - for disclosure in verified.disclosures: - assert disclosure[1] in spliced + assert verified.disclosures[0][1] == "US" From 677651bb2a7febe4d0d1a2421d21d2a822c990bb Mon Sep 17 00:00:00 2001 From: Char Howland Date: Fri, 25 Aug 2023 15:07:51 -0600 Subject: [PATCH 38/85] feat: default to empty list for non sd list Signed-off-by: Char Howland --- aries_cloudagent/wallet/sd_jwt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aries_cloudagent/wallet/sd_jwt.py b/aries_cloudagent/wallet/sd_jwt.py index e5fb80e037..29092a2bf5 100644 --- a/aries_cloudagent/wallet/sd_jwt.py +++ b/aries_cloudagent/wallet/sd_jwt.py @@ -144,7 +144,7 @@ async def sd_jwt_sign( profile: Profile, headers: Mapping[str, Any], payload: Mapping[str, Any], - non_sd_list: List, + non_sd_list: List = [], did: Optional[str] = None, verification_method: Optional[str] = None, ) -> str: From aec0ac0912613fdc692ba27a80c2bec87ea1c4a9 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Fri, 25 Aug 2023 15:23:03 -0600 Subject: [PATCH 39/85] fix: flake8 Signed-off-by: Char Howland --- aries_cloudagent/messaging/valid.py | 2 +- aries_cloudagent/wallet/jwt.py | 3 ++- aries_cloudagent/wallet/sd_jwt.py | 17 ++++++----------- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/aries_cloudagent/messaging/valid.py b/aries_cloudagent/messaging/valid.py index b9fb578954..27c32c6990 100644 --- a/aries_cloudagent/messaging/valid.py +++ b/aries_cloudagent/messaging/valid.py @@ -201,7 +201,7 @@ def __init__(self): class NonSDList(Regexp): - """Validate NonSD List""" + """Validate NonSD List.""" EXAMPLE = [ "name", diff --git a/aries_cloudagent/wallet/jwt.py b/aries_cloudagent/wallet/jwt.py index 34d3eb65d5..b6cf1a2bcc 100644 --- a/aries_cloudagent/wallet/jwt.py +++ b/aries_cloudagent/wallet/jwt.py @@ -105,6 +105,7 @@ def __init__( valid: bool, kid: str, ): + """Initialize a JWTVerifyResult instance.""" self.headers = headers self.payload = payload self.valid = valid @@ -112,7 +113,7 @@ def __init__( class JWTVerifyResultSchema(BaseModelSchema): - """JWTVerifyResult schema""" + """JWTVerifyResult schema.""" class Meta: """JWTVerifyResultSchema metadata.""" diff --git a/aries_cloudagent/wallet/sd_jwt.py b/aries_cloudagent/wallet/sd_jwt.py index 29092a2bf5..0e8149a25a 100644 --- a/aries_cloudagent/wallet/sd_jwt.py +++ b/aries_cloudagent/wallet/sd_jwt.py @@ -69,9 +69,7 @@ async def issue(self): def create_json_paths(it, current_path="", path_list=None): - """ - Create a json path for each element of the payload. - """ + """Create a json path for each element of the payload.""" if path_list is None: path_list = [] @@ -129,9 +127,7 @@ def separate_list_splices(non_sd_list): def create_sd_list(payload, non_sd_list): - """ - Create a list of claims which will be selectively disclosable. - """ + """Create a list of claims which will be selectively disclosable.""" flattened_payload = create_json_paths(payload) separated_non_sd_list = separate_list_splices(non_sd_list) sd_list = [ @@ -188,7 +184,7 @@ async def sd_jwt_sign( class SDJWTVerifyResult(JWTVerifyResult): - """Result from verifying SD-JWT""" + """Result from verifying SD-JWT.""" class Meta: """SDJWTVerifyResult metadata.""" @@ -203,6 +199,7 @@ def __init__( kid, disclosures, ): + """Initialize an SDJWTVerifyResult instance.""" super().__init__( headers, payload, @@ -213,7 +210,7 @@ def __init__( class SDJWTVerifyResultSchema(JWTVerifyResultSchema): - """SDJWTVerifyResult schema""" + """SDJWTVerifyResult schema.""" class Meta: """SDJWTVerifyResultSchema metadata.""" @@ -303,9 +300,7 @@ async def verify(self): async def sd_jwt_verify( profile: Profile, sd_jwt_presentation: str ) -> SDJWTVerifyResult: - """ - Verify sd-jwt using SDJWTVerifierACAPy.verify(). - """ + """Verify sd-jwt using SDJWTVerifierACAPy.verify().""" sd_jwt_verifier = SDJWTVerifierACAPy(profile, sd_jwt_presentation) verified = await sd_jwt_verifier.verify() return verified From 74bcbe06c77f9fd50548cdcb896c27effc1ad51a Mon Sep 17 00:00:00 2001 From: Char Howland Date: Mon, 28 Aug 2023 13:07:03 -0600 Subject: [PATCH 40/85] fix: use atomic group to fix exponential backtracking issue with regex Signed-off-by: Char Howland --- aries_cloudagent/messaging/valid.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aries_cloudagent/messaging/valid.py b/aries_cloudagent/messaging/valid.py index 27c32c6990..7ddba18b6b 100644 --- a/aries_cloudagent/messaging/valid.py +++ b/aries_cloudagent/messaging/valid.py @@ -250,7 +250,7 @@ class SDJSONWebToken(Regexp): "~WyJPMTFySVRjRTdHcXExYW9oRkd0aDh3IiwgIlNBIl0" "~WyJkVmEzX1JlTGNsWTU0R1FHZm5oWlRnIiwgInVwZGF0ZWRfYXQiLCAxNTcwMDAwMDAwXQ" ) - PATTERN = r"^[-_a-zA-Z0-9]*\.[-_a-zA-Z0-9]*\.[-_a-zA-Z0-9]*(~Wy[~a-zA-Z0-9]+)*$" + PATTERN = r"^[-_a-zA-Z0-9]*\.[-_a-zA-Z0-9]*\.[-_a-zA-Z0-9]*(?>~Wy[~a-zA-Z0-9]+)*$" def __init__(self): """Initialize the instance.""" From d9df449e75c4d761a05f61ee43630c51566c41d3 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Mon, 28 Aug 2023 16:54:26 -0600 Subject: [PATCH 41/85] feat: update sd-jwt repo Signed-off-by: Char Howland --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index e12e200aa8..cc6b92f957 100644 --- a/requirements.txt +++ b/requirements.txt @@ -30,4 +30,4 @@ qrcode[pil]~=6.1 requests~=2.31.0 rlp==1.2.0 unflatten~=0.1 -git+https://github.com/danielfett/sd-jwt.git@main +git+https://github.com/openwallet-foundation-labs/sd-jwt-python.git@main \ No newline at end of file From 6ad057cb8ec08a81c9ad53293b2fe2410f5d3109 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Mon, 28 Aug 2023 16:58:04 -0600 Subject: [PATCH 42/85] fix: type hints Signed-off-by: Char Howland --- aries_cloudagent/wallet/sd_jwt.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/aries_cloudagent/wallet/sd_jwt.py b/aries_cloudagent/wallet/sd_jwt.py index 0e8149a25a..1c49f06198 100644 --- a/aries_cloudagent/wallet/sd_jwt.py +++ b/aries_cloudagent/wallet/sd_jwt.py @@ -51,7 +51,7 @@ def __init__( self._serialization_format = serialization_format self.ii_disclosures = [] - async def _create_signed_jws(self): + async def _create_signed_jws(self) -> str: self.serialized_sd_jwt = await jwt_sign( self.profile, self.headers, @@ -60,7 +60,7 @@ async def _create_signed_jws(self): self.verification_method, ) - async def issue(self): + async def issue(self) -> str: """Issue an sd-jwt.""" self._check_for_sd_claim(self._user_claims) self._assemble_sd_jwt_payload() @@ -68,7 +68,7 @@ async def issue(self): self._create_combined() -def create_json_paths(it, current_path="", path_list=None): +def create_json_paths(it, current_path="", path_list=None) -> List: """Create a json path for each element of the payload.""" if path_list is None: path_list = [] @@ -96,7 +96,7 @@ def create_json_paths(it, current_path="", path_list=None): return path_list -def sort_sd_list(sd_list): +def sort_sd_list(sd_list) -> List: """ Sorts sd_list. @@ -108,7 +108,7 @@ def sort_sd_list(sd_list): return [sd[1] for sd in nested_claim_sort] -def separate_list_splices(non_sd_list): +def separate_list_splices(non_sd_list) -> List: """ Separate list splices in the non_sd_list into individual indices. @@ -126,7 +126,7 @@ def separate_list_splices(non_sd_list): return non_sd_list -def create_sd_list(payload, non_sd_list): +def create_sd_list(payload, non_sd_list) -> List: """Create a list of claims which will be selectively disclosable.""" flattened_payload = create_json_paths(payload) separated_non_sd_list = separate_list_splices(non_sd_list) From 8c9323e12615527bdc0511b449c0781f4f3507b4 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Mon, 28 Aug 2023 16:58:40 -0600 Subject: [PATCH 43/85] fix: use isinstance Signed-off-by: Char Howland --- aries_cloudagent/wallet/sd_jwt.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/aries_cloudagent/wallet/sd_jwt.py b/aries_cloudagent/wallet/sd_jwt.py index 1c49f06198..56cb399927 100644 --- a/aries_cloudagent/wallet/sd_jwt.py +++ b/aries_cloudagent/wallet/sd_jwt.py @@ -73,7 +73,7 @@ def create_json_paths(it, current_path="", path_list=None) -> List: if path_list is None: path_list = [] - if type(it) is dict: + if isinstance(it, dict): for k, v in it.items(): new_key = f"{current_path}.{k}" if current_path else k path_list.append(new_key) @@ -86,7 +86,7 @@ def create_json_paths(it, current_path="", path_list=None) -> List: create_json_paths(e, f"{new_key}[{i}]", path_list) else: path_list.append(f"{new_key}[{i}]") - elif type(it) is list: + elif isinstance(it, list): for i, e in enumerate(it): if isinstance(e, (dict, list)): create_json_paths(e, f"{current_path}[{i}]", path_list) @@ -161,7 +161,7 @@ async def sd_jwt_sign( else: for match in matches: if str(match.path) not in CLAIMS_NEVER_SD: - if type(match.context.value) is list: + if isinstance(match.context.value, list): match.context.value.remove(match.value) match.context.value.append(SDObj(match.value)) else: From 04edc50fac916975d5fbb677a6e39d7df9364c5c Mon Sep 17 00:00:00 2001 From: Char Howland Date: Mon, 28 Aug 2023 16:59:42 -0600 Subject: [PATCH 44/85] refactor: return sd_jwt_issuance from .issue() directly Signed-off-by: Char Howland --- aries_cloudagent/wallet/sd_jwt.py | 46 +++++-------------------------- 1 file changed, 7 insertions(+), 39 deletions(-) diff --git a/aries_cloudagent/wallet/sd_jwt.py b/aries_cloudagent/wallet/sd_jwt.py index 56cb399927..369eab57f6 100644 --- a/aries_cloudagent/wallet/sd_jwt.py +++ b/aries_cloudagent/wallet/sd_jwt.py @@ -1,6 +1,5 @@ """Operations supporting SD-JWT creation and verification.""" -import json import re from typing import Any, List, Mapping, Optional from marshmallow import fields @@ -66,6 +65,7 @@ async def issue(self) -> str: self._assemble_sd_jwt_payload() await self._create_signed_jws() self._create_combined() + return self.sd_jwt_issuance def create_json_paths(it, current_path="", path_list=None) -> List: @@ -169,7 +169,7 @@ async def sd_jwt_sign( SDObj(str(match.path)) ] = match.context.value.pop(str(match.path)) - sd_jwt_issuer = SDJWTIssuerACAPy( + return await SDJWTIssuerACAPy( user_claims=payload, issuer_key=None, holder_key=None, @@ -177,10 +177,7 @@ async def sd_jwt_sign( headers=headers, did=did, verification_method=verification_method, - ) - await sd_jwt_issuer.issue() - - return sd_jwt_issuer.sd_jwt_issuance + ).issue() class SDJWTVerifyResult(JWTVerifyResult): @@ -250,7 +247,7 @@ def __init__( async def _verify_sd_jwt(self) -> SDJWTVerifyResult: verified = await jwt_verify( self.profile, - self.serialized_sd_jwt, + self._unverified_input_sd_jwt, ) return SDJWTVerifyResult( headers=verified.headers, @@ -260,40 +257,11 @@ async def _verify_sd_jwt(self) -> SDJWTVerifyResult: disclosures=self._disclosures_list, ) - def _parse_sd_jwt(self, sd_jwt): - if self._serialization_format == "compact": - ( - self._unverified_input_sd_jwt, - *self._input_disclosures, - self._unverified_input_key_binding_jwt, - ) = self._split(sd_jwt) - else: - # if the SD-JWT is in JSON format, parse the json and extract the disclosures. - self._unverified_input_sd_jwt = sd_jwt - self._unverified_input_sd_jwt_parsed = json.loads(sd_jwt) - self._input_disclosures = self._unverified_input_sd_jwt_parsed[ - self.JWS_KEY_DISCLOSURES - ] - self._unverified_input_key_binding_jwt = ( - self._unverified_input_sd_jwt_parsed.get(self.JWS_KEY_KB_JWT, "") - ) - - return self._unverified_input_sd_jwt - - def _create_disclosures_list(self) -> List: - disclosures_list = [] - for disclosure in self._input_disclosures: - disclosures_list.append( - json.loads(self._base64url_decode(disclosure).decode("utf-8")) - ) - - return disclosures_list - - async def verify(self): + async def verify(self) -> SDJWTVerifyResult: """Verify an sd-jwt.""" - self.serialized_sd_jwt = self._parse_sd_jwt(self.sd_jwt_presentation) + self._parse_sd_jwt(self.sd_jwt_presentation) self._create_hash_mappings(self._input_disclosures) - self._disclosures_list = self._create_disclosures_list() + self._disclosures_list = list(self._hash_to_decoded_disclosure.values()) return await self._verify_sd_jwt() From 0d28173ce88461a67e7991f8a48e8bf6ee304c7c Mon Sep 17 00:00:00 2001 From: Char Howland Date: Mon, 28 Aug 2023 17:01:41 -0600 Subject: [PATCH 45/85] test: include subset of SD claims in presentation Signed-off-by: Char Howland --- aries_cloudagent/wallet/tests/test_sd_jwt.py | 36 +++++++++++++++----- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/aries_cloudagent/wallet/tests/test_sd_jwt.py b/aries_cloudagent/wallet/tests/test_sd_jwt.py index fa97d211a4..b1425eb306 100644 --- a/aries_cloudagent/wallet/tests/test_sd_jwt.py +++ b/aries_cloudagent/wallet/tests/test_sd_jwt.py @@ -1,3 +1,5 @@ +from base64 import urlsafe_b64decode +import json import pytest from ...wallet.did_method import KEY @@ -41,10 +43,7 @@ async def test_sign_with_did_key_and_verify(self, profile, in_memory_wallet): "phone_number": "+1-202-555-0101", "phone_number_verified": True, "address": { - "street_address": { - "house_number": "123", - "street": "Main St", - }, + "street_address": "123 Main St", "locality": "Anytown", "region": "Anystate", "country": "US", @@ -59,7 +58,7 @@ async def test_sign_with_did_key_and_verify(self, profile, in_memory_wallet): non_sd_list = [ "given_name", "family_name", - "nationalities", + "birthdate", ] signed = await sd_jwt_sign( profile, @@ -71,7 +70,30 @@ async def test_sign_with_did_key_and_verify(self, profile, in_memory_wallet): ) assert signed - assert await sd_jwt_verify(profile, signed) + # Separate the jwt from the disclosures + signed_sd_jwt = signed.split("~")[0] + + # Determine which selectively disclosable attributes to reveal + revealed = ["sub", "phone_number", "phone_number_verified"] + + for disclosure in signed.split("~")[1:-1]: + # Decode the disclosures + padded = f"{disclosure}{'=' * divmod(len(disclosure),4)[1]}" + decoded = json.loads(urlsafe_b64decode(padded).decode("utf-8")) + # Add the disclosures associated with the claims to be revealed + if decoded[1] in revealed: + signed_sd_jwt = signed_sd_jwt + "~" + disclosure + + verified = await sd_jwt_verify(profile, f"{signed_sd_jwt}~") + assert verified.valid + # Validate that the non-selectively disclosable claims are visible in the payload + assert verified.payload["given_name"] == payload["given_name"] + assert verified.payload["family_name"] == payload["family_name"] + assert verified.payload["birthdate"] == payload["birthdate"] + # Validate that the revealed claims are in the disclosures + assert sorted(revealed) == sorted( + [disclosure[1] for disclosure in verified.disclosures] + ) @pytest.mark.asyncio async def test_flat_structure( @@ -81,8 +103,6 @@ async def test_flat_structure( verification_method = None non_sd_list = [ "address.street_address", - "address.street_address.house_number", - "address.street_address.street", "address.locality", "address.region", "address.country", From 3a4ca88ade83112c4b356f83f157fff5f287755d Mon Sep 17 00:00:00 2001 From: Char Howland Date: Mon, 28 Aug 2023 17:16:45 -0600 Subject: [PATCH 46/85] fix: syntax ?> not supported in the python re module Signed-off-by: Char Howland --- aries_cloudagent/messaging/valid.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aries_cloudagent/messaging/valid.py b/aries_cloudagent/messaging/valid.py index 7ddba18b6b..9b63103be1 100644 --- a/aries_cloudagent/messaging/valid.py +++ b/aries_cloudagent/messaging/valid.py @@ -250,7 +250,7 @@ class SDJSONWebToken(Regexp): "~WyJPMTFySVRjRTdHcXExYW9oRkd0aDh3IiwgIlNBIl0" "~WyJkVmEzX1JlTGNsWTU0R1FHZm5oWlRnIiwgInVwZGF0ZWRfYXQiLCAxNTcwMDAwMDAwXQ" ) - PATTERN = r"^[-_a-zA-Z0-9]*\.[-_a-zA-Z0-9]*\.[-_a-zA-Z0-9]*(?>~Wy[~a-zA-Z0-9]+)*$" + PATTERN = r"^[-_a-zA-Z0-9]*\.[-_a-zA-Z0-9]*\.[-_a-zA-Z0-9]*(?:~Wy[~a-zA-Z0-9]+)*$" def __init__(self): """Initialize the instance.""" From 3fca3843c94ff51df125716e2f6e921fd4a221d3 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Tue, 29 Aug 2023 10:05:25 -0600 Subject: [PATCH 47/85] fix: remove Wy prefix from sd-jwt regex Signed-off-by: Char Howland --- aries_cloudagent/messaging/valid.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aries_cloudagent/messaging/valid.py b/aries_cloudagent/messaging/valid.py index 9b63103be1..476e528bb6 100644 --- a/aries_cloudagent/messaging/valid.py +++ b/aries_cloudagent/messaging/valid.py @@ -250,7 +250,7 @@ class SDJSONWebToken(Regexp): "~WyJPMTFySVRjRTdHcXExYW9oRkd0aDh3IiwgIlNBIl0" "~WyJkVmEzX1JlTGNsWTU0R1FHZm5oWlRnIiwgInVwZGF0ZWRfYXQiLCAxNTcwMDAwMDAwXQ" ) - PATTERN = r"^[-_a-zA-Z0-9]*\.[-_a-zA-Z0-9]*\.[-_a-zA-Z0-9]*(?:~Wy[~a-zA-Z0-9]+)*$" + PATTERN = r"^[-_a-zA-Z0-9]*\.[-_a-zA-Z0-9]*\.[-_a-zA-Z0-9]*~[a-zA-Z0-9~]*$" def __init__(self): """Initialize the instance.""" From 2553a6b2745c4926f1bf365e3aaa3384ee1d97a8 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Tue, 29 Aug 2023 12:35:09 -0600 Subject: [PATCH 48/85] fix: ensure values between ~ delimiters in sd-jwt regex Signed-off-by: Char Howland --- aries_cloudagent/messaging/valid.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aries_cloudagent/messaging/valid.py b/aries_cloudagent/messaging/valid.py index 476e528bb6..6607e1e1a5 100644 --- a/aries_cloudagent/messaging/valid.py +++ b/aries_cloudagent/messaging/valid.py @@ -250,7 +250,7 @@ class SDJSONWebToken(Regexp): "~WyJPMTFySVRjRTdHcXExYW9oRkd0aDh3IiwgIlNBIl0" "~WyJkVmEzX1JlTGNsWTU0R1FHZm5oWlRnIiwgInVwZGF0ZWRfYXQiLCAxNTcwMDAwMDAwXQ" ) - PATTERN = r"^[-_a-zA-Z0-9]*\.[-_a-zA-Z0-9]*\.[-_a-zA-Z0-9]*~[a-zA-Z0-9~]*$" + PATTERN = r"^[-_a-zA-Z0-9]+\.[-_a-zA-Z0-9]+\.[-_a-zA-Z0-9]+(?:~[a-zA-Z0-9]+)*~?$" def __init__(self): """Initialize the instance.""" From 31b7dc2546ab6b0a1532a79556d0c65c4768ded9 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Tue, 29 Aug 2023 12:48:07 -0600 Subject: [PATCH 49/85] fix: include full character set for urlsafe base64 encoded data Signed-off-by: Char Howland --- aries_cloudagent/messaging/valid.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/aries_cloudagent/messaging/valid.py b/aries_cloudagent/messaging/valid.py index 6607e1e1a5..342d5fb62f 100644 --- a/aries_cloudagent/messaging/valid.py +++ b/aries_cloudagent/messaging/valid.py @@ -250,7 +250,9 @@ class SDJSONWebToken(Regexp): "~WyJPMTFySVRjRTdHcXExYW9oRkd0aDh3IiwgIlNBIl0" "~WyJkVmEzX1JlTGNsWTU0R1FHZm5oWlRnIiwgInVwZGF0ZWRfYXQiLCAxNTcwMDAwMDAwXQ" ) - PATTERN = r"^[-_a-zA-Z0-9]+\.[-_a-zA-Z0-9]+\.[-_a-zA-Z0-9]+(?:~[a-zA-Z0-9]+)*~?$" + PATTERN = ( + r"^[-_a-zA-Z0-9]+\.[-_a-zA-Z0-9]+\.[-_a-zA-Z0-9]+(?:~[a-zA-Z0-9\+/=\._-]+)*~?$" + ) def __init__(self): """Initialize the instance.""" From 95be51c81723443b16e70e41041adb35bd72f457 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Tue, 29 Aug 2023 13:19:13 -0600 Subject: [PATCH 50/85] fix: include full urlsafe b64 encoding character set for jwts Signed-off-by: Char Howland --- aries_cloudagent/messaging/valid.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/aries_cloudagent/messaging/valid.py b/aries_cloudagent/messaging/valid.py index 342d5fb62f..3188af5418 100644 --- a/aries_cloudagent/messaging/valid.py +++ b/aries_cloudagent/messaging/valid.py @@ -228,7 +228,7 @@ class JSONWebToken(Regexp): "eyJhIjogIjAifQ." "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk" ) - PATTERN = r"^[-_a-zA-Z0-9]*\.[-_a-zA-Z0-9]*\.[-_a-zA-Z0-9]*$" + PATTERN = r"^[a-zA-Z0-9\+/=\._-]+\.[a-zA-Z0-9\+/=\._-]+\.[a-zA-Z0-9\+/=\._-]+$" def __init__(self): """Initialize the instance.""" @@ -250,9 +250,8 @@ class SDJSONWebToken(Regexp): "~WyJPMTFySVRjRTdHcXExYW9oRkd0aDh3IiwgIlNBIl0" "~WyJkVmEzX1JlTGNsWTU0R1FHZm5oWlRnIiwgInVwZGF0ZWRfYXQiLCAxNTcwMDAwMDAwXQ" ) - PATTERN = ( - r"^[-_a-zA-Z0-9]+\.[-_a-zA-Z0-9]+\.[-_a-zA-Z0-9]+(?:~[a-zA-Z0-9\+/=\._-]+)*~?$" - ) + PATTERN = r"""^[a-zA-Z0-9\+/=\._-]+\.[a-zA-Z0-9\+/=\._-]+\.[a-zA-Z0-9\+/=\._-]+ + (?:~[a-zA-Z0-9\+/=\._-]+)*~?$""" def __init__(self): """Initialize the instance.""" From 2986a77fadfdc480d27546ede0f0c380fe0747ba Mon Sep 17 00:00:00 2001 From: Char Howland Date: Tue, 29 Aug 2023 14:18:47 -0600 Subject: [PATCH 51/85] fix: remove some special characters from jwt regex Signed-off-by: Char Howland --- aries_cloudagent/messaging/valid.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/aries_cloudagent/messaging/valid.py b/aries_cloudagent/messaging/valid.py index 3188af5418..3487732e37 100644 --- a/aries_cloudagent/messaging/valid.py +++ b/aries_cloudagent/messaging/valid.py @@ -228,7 +228,7 @@ class JSONWebToken(Regexp): "eyJhIjogIjAifQ." "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk" ) - PATTERN = r"^[a-zA-Z0-9\+/=\._-]+\.[a-zA-Z0-9\+/=\._-]+\.[a-zA-Z0-9\+/=\._-]+$" + PATTERN = r"^[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+$" def __init__(self): """Initialize the instance.""" @@ -250,8 +250,7 @@ class SDJSONWebToken(Regexp): "~WyJPMTFySVRjRTdHcXExYW9oRkd0aDh3IiwgIlNBIl0" "~WyJkVmEzX1JlTGNsWTU0R1FHZm5oWlRnIiwgInVwZGF0ZWRfYXQiLCAxNTcwMDAwMDAwXQ" ) - PATTERN = r"""^[a-zA-Z0-9\+/=\._-]+\.[a-zA-Z0-9\+/=\._-]+\.[a-zA-Z0-9\+/=\._-]+ - (?:~[a-zA-Z0-9\+/=\._-]+)*~?$""" + PATTERN = r"^[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+(?:~[a-zA-Z0-9_-]+)*~?$" def __init__(self): """Initialize the instance.""" From 889da11509a40229c4aaa2f03ebd411e3f63b3eb Mon Sep 17 00:00:00 2001 From: Char Howland Date: Tue, 29 Aug 2023 16:56:05 -0600 Subject: [PATCH 52/85] fix: jwt regex fix Signed-off-by: Char Howland --- aries_cloudagent/messaging/valid.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aries_cloudagent/messaging/valid.py b/aries_cloudagent/messaging/valid.py index 3487732e37..ab1010f3b6 100644 --- a/aries_cloudagent/messaging/valid.py +++ b/aries_cloudagent/messaging/valid.py @@ -228,7 +228,7 @@ class JSONWebToken(Regexp): "eyJhIjogIjAifQ." "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk" ) - PATTERN = r"^[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+$" + PATTERN = r"^[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]*\.[a-zA-Z0-9_-]+$" def __init__(self): """Initialize the instance.""" @@ -250,7 +250,7 @@ class SDJSONWebToken(Regexp): "~WyJPMTFySVRjRTdHcXExYW9oRkd0aDh3IiwgIlNBIl0" "~WyJkVmEzX1JlTGNsWTU0R1FHZm5oWlRnIiwgInVwZGF0ZWRfYXQiLCAxNTcwMDAwMDAwXQ" ) - PATTERN = r"^[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+(?:~[a-zA-Z0-9_-]+)*~?$" + PATTERN = r"^[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]*\.[a-zA-Z0-9_-]+(?:~[a-zA-Z0-9_-]+)*~?$" def __init__(self): """Initialize the instance.""" From c7214c2c5efa32b6debd24e0136d7d447b16b015 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Thu, 7 Sep 2023 12:14:31 -0600 Subject: [PATCH 53/85] fix: prevent overwriting "typ" key in jwt_sign headers Signed-off-by: Char Howland --- aries_cloudagent/wallet/jwt.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/aries_cloudagent/wallet/jwt.py b/aries_cloudagent/wallet/jwt.py index b6cf1a2bcc..3137ec7bfa 100644 --- a/aries_cloudagent/wallet/jwt.py +++ b/aries_cloudagent/wallet/jwt.py @@ -69,10 +69,11 @@ async def jwt_sign( if not did: raise ValueError("DID URL must be absolute") + if not headers.get("typ", None): + headers["typ"] = "JWT" headers = { **headers, "alg": "EdDSA", - "typ": "JWT", "kid": verification_method, } encoded_headers = dict_to_b64(headers) From c84208f321895ac959e2d4fd3cdfed4216802ca6 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Thu, 7 Sep 2023 12:15:40 -0600 Subject: [PATCH 54/85] fix: prevent adding always visible claims to sd_jwt Signed-off-by: Char Howland --- aries_cloudagent/wallet/sd_jwt.py | 38 +++++++++++++++---------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/aries_cloudagent/wallet/sd_jwt.py b/aries_cloudagent/wallet/sd_jwt.py index 369eab57f6..a1b9c11061 100644 --- a/aries_cloudagent/wallet/sd_jwt.py +++ b/aries_cloudagent/wallet/sd_jwt.py @@ -75,17 +75,18 @@ def create_json_paths(it, current_path="", path_list=None) -> List: if isinstance(it, dict): for k, v in it.items(): - new_key = f"{current_path}.{k}" if current_path else k - path_list.append(new_key) - - if isinstance(v, dict): - create_json_paths(v, new_key, path_list) - elif isinstance(v, list): - for i, e in enumerate(v): - if isinstance(e, (dict, list)): - create_json_paths(e, f"{new_key}[{i}]", path_list) - else: - path_list.append(f"{new_key}[{i}]") + if not k.startswith(tuple(CLAIMS_NEVER_SD)): + new_key = f"{current_path}.{k}" if current_path else k + path_list.append(new_key) + + if isinstance(v, dict): + create_json_paths(v, new_key, path_list) + elif isinstance(v, list): + for i, e in enumerate(v): + if isinstance(e, (dict, list)): + create_json_paths(e, f"{new_key}[{i}]", path_list) + else: + path_list.append(f"{new_key}[{i}]") elif isinstance(it, list): for i, e in enumerate(it): if isinstance(e, (dict, list)): @@ -160,14 +161,13 @@ async def sd_jwt_sign( raise SDJWTError(f"Claim for {sd} not found in payload.") else: for match in matches: - if str(match.path) not in CLAIMS_NEVER_SD: - if isinstance(match.context.value, list): - match.context.value.remove(match.value) - match.context.value.append(SDObj(match.value)) - else: - match.context.value[ - SDObj(str(match.path)) - ] = match.context.value.pop(str(match.path)) + if isinstance(match.context.value, list): + match.context.value.remove(match.value) + match.context.value.append(SDObj(match.value)) + else: + match.context.value[ + SDObj(str(match.path)) + ] = match.context.value.pop(str(match.path)) return await SDJWTIssuerACAPy( user_claims=payload, From 74cec2e0f523f11970d59b8c48d2480f23423273 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Thu, 7 Sep 2023 12:17:11 -0600 Subject: [PATCH 55/85] feat: add expected_nonce, expected_aud as arguments Signed-off-by: Char Howland --- aries_cloudagent/wallet/sd_jwt.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/aries_cloudagent/wallet/sd_jwt.py b/aries_cloudagent/wallet/sd_jwt.py index a1b9c11061..1415204e8b 100644 --- a/aries_cloudagent/wallet/sd_jwt.py +++ b/aries_cloudagent/wallet/sd_jwt.py @@ -1,7 +1,7 @@ """Operations supporting SD-JWT creation and verification.""" import re -from typing import Any, List, Mapping, Optional +from typing import Any, List, Mapping, Optional, Union from marshmallow import fields from jsonpath_ng.ext import parse from sd_jwt.common import SDObj @@ -237,12 +237,16 @@ def __init__( self, profile: Profile, sd_jwt_presentation: str, + expected_aud: Union[str, None] = None, + expected_nonce: Union[str, None] = None, serialization_format: str = "compact", ): """Initialize an SDJWTVerifierACAPy instance.""" self.profile = profile self.sd_jwt_presentation = sd_jwt_presentation self._serialization_format = serialization_format + self.expected_aud = expected_aud + self.expected_nonce = expected_nonce async def _verify_sd_jwt(self) -> SDJWTVerifyResult: verified = await jwt_verify( @@ -266,9 +270,13 @@ async def verify(self) -> SDJWTVerifyResult: async def sd_jwt_verify( - profile: Profile, sd_jwt_presentation: str + profile: Profile, + sd_jwt_presentation: str, + expected_aud: str = None, + expected_nonce: str = None, ) -> SDJWTVerifyResult: """Verify sd-jwt using SDJWTVerifierACAPy.verify().""" - sd_jwt_verifier = SDJWTVerifierACAPy(profile, sd_jwt_presentation) - verified = await sd_jwt_verifier.verify() - return verified + sd_jwt_verifier = SDJWTVerifierACAPy( + profile, sd_jwt_presentation, expected_aud, expected_nonce + ) + return await sd_jwt_verifier.verify() From 47aabcae7e5311bc8d18886695fa5717b19b90f0 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Thu, 7 Sep 2023 12:19:06 -0600 Subject: [PATCH 56/85] feat: redefine _verify_key_binding_jwt Signed-off-by: Char Howland --- aries_cloudagent/wallet/sd_jwt.py | 43 ++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/aries_cloudagent/wallet/sd_jwt.py b/aries_cloudagent/wallet/sd_jwt.py index 1415204e8b..817055e24f 100644 --- a/aries_cloudagent/wallet/sd_jwt.py +++ b/aries_cloudagent/wallet/sd_jwt.py @@ -266,7 +266,48 @@ async def verify(self) -> SDJWTVerifyResult: self._parse_sd_jwt(self.sd_jwt_presentation) self._create_hash_mappings(self._input_disclosures) self._disclosures_list = list(self._hash_to_decoded_disclosure.values()) - return await self._verify_sd_jwt() + + self.verified_sd_jwt = await self._verify_sd_jwt() + + if self.expected_aud or self.expected_nonce: + if not (self.expected_aud and self.expected_nonce): + raise ValueError( + "Either both expected_aud and expected_nonce must be provided " + "or both must be None" + ) + await self._verify_key_binding_jwt( + self.expected_aud, + self.expected_nonce, + ) + return self.verified_sd_jwt + + async def _verify_key_binding_jwt( + self, + expected_aud: Union[str, None] = None, + expected_nonce: Union[str, None] = None, + ): + verified_kb_jwt = await jwt_verify( + self.profile, self._unverified_input_key_binding_jwt + ) + self._holder_public_key_payload = self.verified_sd_jwt.payload.get("cnf", None) + + if not self._holder_public_key_payload: + raise ValueError("No holder public key in SD-JWT") + + holder_public_key_payload_jwk = self._holder_public_key_payload.get("jwk", None) + if not holder_public_key_payload_jwk: + raise ValueError( + "The holder_public_key_payload is malformed. " + "It doesn't contain the claim jwk: " + f"{self._holder_public_key_payload}" + ) + + if verified_kb_jwt.headers["typ"] != self.KB_JWT_TYP_HEADER: + raise ValueError("Invalid header typ") + if verified_kb_jwt.payload["aud"] != expected_aud: + raise ValueError("Invalid audience") + if verified_kb_jwt.payload["nonce"] != expected_nonce: + raise ValueError("Invalid nonce") async def sd_jwt_verify( From a7993fcee8a19803659ec6c396157307e005c6c8 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Thu, 7 Sep 2023 12:21:32 -0600 Subject: [PATCH 57/85] test: key binding implementation Signed-off-by: Char Howland --- aries_cloudagent/wallet/tests/test_sd_jwt.py | 223 +++++++++++++++++++ 1 file changed, 223 insertions(+) diff --git a/aries_cloudagent/wallet/tests/test_sd_jwt.py b/aries_cloudagent/wallet/tests/test_sd_jwt.py index b1425eb306..8e0cf10137 100644 --- a/aries_cloudagent/wallet/tests/test_sd_jwt.py +++ b/aries_cloudagent/wallet/tests/test_sd_jwt.py @@ -2,8 +2,10 @@ import json import pytest + from ...wallet.did_method import KEY from ...wallet.key_type import ED25519 +from ...wallet.jwt import jwt_sign from ..sd_jwt import SDJWTVerifyResult, sd_jwt_sign, sd_jwt_verify @@ -94,6 +96,9 @@ async def test_sign_with_did_key_and_verify(self, profile, in_memory_wallet): assert sorted(revealed) == sorted( [disclosure[1] for disclosure in verified.disclosures] ) + assert verified.payload["iss"] == payload["iss"] + assert verified.payload["iat"] == payload["iat"] + assert verified.payload["exp"] == payload["exp"] @pytest.mark.asyncio async def test_flat_structure( @@ -216,3 +221,221 @@ async def test_list_splice(self, profile, in_memory_wallet): assert nationality in payload["nationalities"] assert verified.payload["_sd_alg"] assert verified.disclosures[0][1] == "US" + + @pytest.mark.asyncio + async def test_sd_jwt_key_binding(self, profile, in_memory_wallet): + did_info = await in_memory_wallet.create_local_did(KEY, ED25519, self.seed) + verification_method = None + + payload = { + "given_name": "John", + "family_name": "Doe", + "iss": "https://example.com/issuer", + "iat": 1683000000, + "exp": 1883000000, + "cnf": { + "jwk": { + "kty": "EC", + "crv": "P-256", + "x": "TCAER19Zvu3OHF4j4W4vfSVoHIP1ILilDls7vCeGemc", + "y": "ZxjiWWbZMQGHVWKVQ4hbSIirsVfuecCE6t4jT9F2HZQ", + } + }, + } + signed = await sd_jwt_sign( + profile, + self.headers, + payload, + did=did_info.did, + verification_method=verification_method, + ) + assert signed + + # Key binding + headers_kb = {"alg": "ES256", "typ": "kb+jwt"} + payload_kb = { + "nonce": "1234567890", + "aud": "https://example.com/verifier", + "iat": 1688160483, + } + signed_kb = await jwt_sign( + profile, + headers_kb, + payload_kb, + did_info.did, + verification_method, + ) + assert signed_kb + + assert await sd_jwt_verify( + profile, + f"{signed}{signed_kb}", + expected_aud=payload_kb["aud"], + expected_nonce=payload_kb["nonce"], + ) + + test_input = [ + ( + "Either both expected_aud and expected_nonce must be provided or both must be None", + { + "given_name": "John", + "family_name": "Doe", + "iss": "https://example.com/issuer", + "iat": 1683000000, + "exp": 1883000000, + "cnf": { + "jwk": { + "kty": "EC", + "crv": "P-256", + "x": "TCAER19Zvu3OHF4j4W4vfSVoHIP1ILilDls7vCeGemc", + "y": "ZxjiWWbZMQGHVWKVQ4hbSIirsVfuecCE6t4jT9F2HZQ", + } + }, + }, + {"alg": "ES256", "typ": "kb+jwt"}, + "https://example.com/verifier", + None, + ), + ( + "No holder public key in SD-JWT", + { + "given_name": "John", + "family_name": "Doe", + "iss": "https://example.com/issuer", + "iat": 1683000000, + "exp": 1883000000, + }, + {"alg": "ES256", "typ": "kb+jwt"}, + "https://example.com/verifier", + "1234567890", + ), + ( + "The holder_public_key_payload is malformed. It doesn't contain the claim jwk: ", + { + "given_name": "John", + "family_name": "Doe", + "iss": "https://example.com/issuer", + "iat": 1683000000, + "exp": 1883000000, + "cnf": {"y": "ZxjiWWbZMQGHVWKVQ4hbSIirsVfuecCE6t4jT9F2HZQ"}, + }, + {"alg": "ES256", "typ": "kb+jwt"}, + "https://example.com/verifier", + "1234567890", + ), + ( + "Invalid header typ", + { + "given_name": "John", + "family_name": "Doe", + "iss": "https://example.com/issuer", + "iat": 1683000000, + "exp": 1883000000, + "cnf": { + "jwk": { + "kty": "EC", + "crv": "P-256", + "x": "TCAER19Zvu3OHF4j4W4vfSVoHIP1ILilDls7vCeGemc", + "y": "ZxjiWWbZMQGHVWKVQ4hbSIirsVfuecCE6t4jT9F2HZQ", + } + }, + }, + {"alg": "ES256", "typ": "JWT"}, + "https://example.com/verifier", + "1234567890", + ), + ( + "Invalid audience", + { + "given_name": "John", + "family_name": "Doe", + "iss": "https://example.com/issuer", + "iat": 1683000000, + "exp": 1883000000, + "cnf": { + "jwk": { + "kty": "EC", + "crv": "P-256", + "x": "TCAER19Zvu3OHF4j4W4vfSVoHIP1ILilDls7vCeGemc", + "y": "ZxjiWWbZMQGHVWKVQ4hbSIirsVfuecCE6t4jT9F2HZQ", + } + }, + }, + {"alg": "ES256", "typ": "kb+jwt"}, + "invalid_aud", + "1234567890", + ), + ( + "Invalid nonce", + { + "given_name": "John", + "family_name": "Doe", + "iss": "https://example.com/issuer", + "iat": 1683000000, + "exp": 1883000000, + "cnf": { + "jwk": { + "kty": "EC", + "crv": "P-256", + "x": "TCAER19Zvu3OHF4j4W4vfSVoHIP1ILilDls7vCeGemc", + "y": "ZxjiWWbZMQGHVWKVQ4hbSIirsVfuecCE6t4jT9F2HZQ", + } + }, + }, + {"alg": "ES256", "typ": "kb+jwt"}, + "https://example.com/verifier", + "invalid_nonce", + ), + ] + + @pytest.mark.parametrize( + "error, payload, headers_kb, expected_aud, expected_nonce", test_input + ) + @pytest.mark.asyncio + async def test_sd_jwt_key_binding_errors( + self, + payload, + error, + expected_nonce, + headers_kb, + expected_aud, + profile, + in_memory_wallet, + ): + did_info = await in_memory_wallet.create_local_did(KEY, ED25519, self.seed) + verification_method = None + + signed = await sd_jwt_sign( + profile, + self.headers, + payload, + did=did_info.did, + verification_method=verification_method, + ) + assert signed + + # Key binding + payload_kb = { + "nonce": "1234567890", + "aud": "https://example.com/verifier", + "iat": 1688160483, + } + signed_kb = await jwt_sign( + profile, + headers_kb, + payload_kb, + did_info.did, + verification_method, + ) + assert signed_kb + + with pytest.raises( + ValueError, + match=error, + ): + await sd_jwt_verify( + profile, + f"{signed}{signed_kb}", + expected_aud=expected_aud, + expected_nonce=expected_nonce, + ) From 3141e459dee91a5f0c8a73816eaf69438d0bc1bf Mon Sep 17 00:00:00 2001 From: Char Howland Date: Thu, 7 Sep 2023 17:08:16 -0600 Subject: [PATCH 58/85] feat: add sd-jwt python library to pyproject.toml Signed-off-by: Char Howland --- poetry.lock | 305 +++++++++++++++++++++++++++++++++++++++---------- pyproject.toml | 1 + 2 files changed, 245 insertions(+), 61 deletions(-) diff --git a/poetry.lock b/poetry.lock index 03c7d1cf76..d57e8e0298 100644 --- a/poetry.lock +++ b/poetry.lock @@ -648,68 +648,113 @@ yaml = ["PyYAML"] [[package]] name = "coverage" -version = "7.3.0" +version = "7.3.1" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:db76a1bcb51f02b2007adacbed4c88b6dee75342c37b05d1822815eed19edee5"}, - {file = "coverage-7.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c02cfa6c36144ab334d556989406837336c1d05215a9bdf44c0bc1d1ac1cb637"}, - {file = "coverage-7.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:477c9430ad5d1b80b07f3c12f7120eef40bfbf849e9e7859e53b9c93b922d2af"}, - {file = "coverage-7.3.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce2ee86ca75f9f96072295c5ebb4ef2a43cecf2870b0ca5e7a1cbdd929cf67e1"}, - {file = "coverage-7.3.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68d8a0426b49c053013e631c0cdc09b952d857efa8f68121746b339912d27a12"}, - {file = "coverage-7.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b3eb0c93e2ea6445b2173da48cb548364f8f65bf68f3d090404080d338e3a689"}, - {file = "coverage-7.3.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:90b6e2f0f66750c5a1178ffa9370dec6c508a8ca5265c42fbad3ccac210a7977"}, - {file = "coverage-7.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:96d7d761aea65b291a98c84e1250cd57b5b51726821a6f2f8df65db89363be51"}, - {file = "coverage-7.3.0-cp310-cp310-win32.whl", hash = "sha256:63c5b8ecbc3b3d5eb3a9d873dec60afc0cd5ff9d9f1c75981d8c31cfe4df8527"}, - {file = "coverage-7.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:97c44f4ee13bce914272589b6b41165bbb650e48fdb7bd5493a38bde8de730a1"}, - {file = "coverage-7.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:74c160285f2dfe0acf0f72d425f3e970b21b6de04157fc65adc9fd07ee44177f"}, - {file = "coverage-7.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b543302a3707245d454fc49b8ecd2c2d5982b50eb63f3535244fd79a4be0c99d"}, - {file = "coverage-7.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad0f87826c4ebd3ef484502e79b39614e9c03a5d1510cfb623f4a4a051edc6fd"}, - {file = "coverage-7.3.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:13c6cbbd5f31211d8fdb477f0f7b03438591bdd077054076eec362cf2207b4a7"}, - {file = "coverage-7.3.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fac440c43e9b479d1241fe9d768645e7ccec3fb65dc3a5f6e90675e75c3f3e3a"}, - {file = "coverage-7.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:3c9834d5e3df9d2aba0275c9f67989c590e05732439b3318fa37a725dff51e74"}, - {file = "coverage-7.3.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4c8e31cf29b60859876474034a83f59a14381af50cbe8a9dbaadbf70adc4b214"}, - {file = "coverage-7.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7a9baf8e230f9621f8e1d00c580394a0aa328fdac0df2b3f8384387c44083c0f"}, - {file = "coverage-7.3.0-cp311-cp311-win32.whl", hash = "sha256:ccc51713b5581e12f93ccb9c5e39e8b5d4b16776d584c0f5e9e4e63381356482"}, - {file = "coverage-7.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:887665f00ea4e488501ba755a0e3c2cfd6278e846ada3185f42d391ef95e7e70"}, - {file = "coverage-7.3.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d000a739f9feed900381605a12a61f7aaced6beae832719ae0d15058a1e81c1b"}, - {file = "coverage-7.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:59777652e245bb1e300e620ce2bef0d341945842e4eb888c23a7f1d9e143c446"}, - {file = "coverage-7.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9737bc49a9255d78da085fa04f628a310c2332b187cd49b958b0e494c125071"}, - {file = "coverage-7.3.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5247bab12f84a1d608213b96b8af0cbb30d090d705b6663ad794c2f2a5e5b9fe"}, - {file = "coverage-7.3.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2ac9a1de294773b9fa77447ab7e529cf4fe3910f6a0832816e5f3d538cfea9a"}, - {file = "coverage-7.3.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:85b7335c22455ec12444cec0d600533a238d6439d8d709d545158c1208483873"}, - {file = "coverage-7.3.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:36ce5d43a072a036f287029a55b5c6a0e9bd73db58961a273b6dc11a2c6eb9c2"}, - {file = "coverage-7.3.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:211a4576e984f96d9fce61766ffaed0115d5dab1419e4f63d6992b480c2bd60b"}, - {file = "coverage-7.3.0-cp312-cp312-win32.whl", hash = "sha256:56afbf41fa4a7b27f6635bc4289050ac3ab7951b8a821bca46f5b024500e6321"}, - {file = "coverage-7.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:7f297e0c1ae55300ff688568b04ff26b01c13dfbf4c9d2b7d0cb688ac60df479"}, - {file = "coverage-7.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ac0dec90e7de0087d3d95fa0533e1d2d722dcc008bc7b60e1143402a04c117c1"}, - {file = "coverage-7.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:438856d3f8f1e27f8e79b5410ae56650732a0dcfa94e756df88c7e2d24851fcd"}, - {file = "coverage-7.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1084393c6bda8875c05e04fce5cfe1301a425f758eb012f010eab586f1f3905e"}, - {file = "coverage-7.3.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49ab200acf891e3dde19e5aa4b0f35d12d8b4bd805dc0be8792270c71bd56c54"}, - {file = "coverage-7.3.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a67e6bbe756ed458646e1ef2b0778591ed4d1fcd4b146fc3ba2feb1a7afd4254"}, - {file = "coverage-7.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8f39c49faf5344af36042b293ce05c0d9004270d811c7080610b3e713251c9b0"}, - {file = "coverage-7.3.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:7df91fb24c2edaabec4e0eee512ff3bc6ec20eb8dccac2e77001c1fe516c0c84"}, - {file = "coverage-7.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:34f9f0763d5fa3035a315b69b428fe9c34d4fc2f615262d6be3d3bf3882fb985"}, - {file = "coverage-7.3.0-cp38-cp38-win32.whl", hash = "sha256:bac329371d4c0d456e8d5f38a9b0816b446581b5f278474e416ea0c68c47dcd9"}, - {file = "coverage-7.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:b859128a093f135b556b4765658d5d2e758e1fae3e7cc2f8c10f26fe7005e543"}, - {file = "coverage-7.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fc0ed8d310afe013db1eedd37176d0839dc66c96bcfcce8f6607a73ffea2d6ba"}, - {file = "coverage-7.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61260ec93f99f2c2d93d264b564ba912bec502f679793c56f678ba5251f0393"}, - {file = "coverage-7.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97af9554a799bd7c58c0179cc8dbf14aa7ab50e1fd5fa73f90b9b7215874ba28"}, - {file = "coverage-7.3.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3558e5b574d62f9c46b76120a5c7c16c4612dc2644c3d48a9f4064a705eaee95"}, - {file = "coverage-7.3.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37d5576d35fcb765fca05654f66aa71e2808d4237d026e64ac8b397ffa66a56a"}, - {file = "coverage-7.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:07ea61bcb179f8f05ffd804d2732b09d23a1238642bf7e51dad62082b5019b34"}, - {file = "coverage-7.3.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:80501d1b2270d7e8daf1b64b895745c3e234289e00d5f0e30923e706f110334e"}, - {file = "coverage-7.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4eddd3153d02204f22aef0825409091a91bf2a20bce06fe0f638f5c19a85de54"}, - {file = "coverage-7.3.0-cp39-cp39-win32.whl", hash = "sha256:2d22172f938455c156e9af2612650f26cceea47dc86ca048fa4e0b2d21646ad3"}, - {file = "coverage-7.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:60f64e2007c9144375dd0f480a54d6070f00bb1a28f65c408370544091c9bc9e"}, - {file = "coverage-7.3.0-pp38.pp39.pp310-none-any.whl", hash = "sha256:5492a6ce3bdb15c6ad66cb68a0244854d9917478877a25671d70378bdc8562d0"}, - {file = "coverage-7.3.0.tar.gz", hash = "sha256:49dbb19cdcafc130f597d9e04a29d0a032ceedf729e41b181f51cd170e6ee865"}, + {file = "coverage-7.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cd0f7429ecfd1ff597389907045ff209c8fdb5b013d38cfa7c60728cb484b6e3"}, + {file = "coverage-7.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:966f10df9b2b2115da87f50f6a248e313c72a668248be1b9060ce935c871f276"}, + {file = "coverage-7.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0575c37e207bb9b98b6cf72fdaaa18ac909fb3d153083400c2d48e2e6d28bd8e"}, + {file = "coverage-7.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:245c5a99254e83875c7fed8b8b2536f040997a9b76ac4c1da5bff398c06e860f"}, + {file = "coverage-7.3.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c96dd7798d83b960afc6c1feb9e5af537fc4908852ef025600374ff1a017392"}, + {file = "coverage-7.3.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:de30c1aa80f30af0f6b2058a91505ea6e36d6535d437520067f525f7df123887"}, + {file = "coverage-7.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:50dd1e2dd13dbbd856ffef69196781edff26c800a74f070d3b3e3389cab2600d"}, + {file = "coverage-7.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b9c0c19f70d30219113b18fe07e372b244fb2a773d4afde29d5a2f7930765136"}, + {file = "coverage-7.3.1-cp310-cp310-win32.whl", hash = "sha256:770f143980cc16eb601ccfd571846e89a5fe4c03b4193f2e485268f224ab602f"}, + {file = "coverage-7.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:cdd088c00c39a27cfa5329349cc763a48761fdc785879220d54eb785c8a38520"}, + {file = "coverage-7.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:74bb470399dc1989b535cb41f5ca7ab2af561e40def22d7e188e0a445e7639e3"}, + {file = "coverage-7.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:025ded371f1ca280c035d91b43252adbb04d2aea4c7105252d3cbc227f03b375"}, + {file = "coverage-7.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6191b3a6ad3e09b6cfd75b45c6aeeffe7e3b0ad46b268345d159b8df8d835f9"}, + {file = "coverage-7.3.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7eb0b188f30e41ddd659a529e385470aa6782f3b412f860ce22b2491c89b8593"}, + {file = "coverage-7.3.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75c8f0df9dfd8ff745bccff75867d63ef336e57cc22b2908ee725cc552689ec8"}, + {file = "coverage-7.3.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7eb3cd48d54b9bd0e73026dedce44773214064be93611deab0b6a43158c3d5a0"}, + {file = "coverage-7.3.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:ac3c5b7e75acac31e490b7851595212ed951889918d398b7afa12736c85e13ce"}, + {file = "coverage-7.3.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5b4ee7080878077af0afa7238df1b967f00dc10763f6e1b66f5cced4abebb0a3"}, + {file = "coverage-7.3.1-cp311-cp311-win32.whl", hash = "sha256:229c0dd2ccf956bf5aeede7e3131ca48b65beacde2029f0361b54bf93d36f45a"}, + {file = "coverage-7.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:c6f55d38818ca9596dc9019eae19a47410d5322408140d9a0076001a3dcb938c"}, + {file = "coverage-7.3.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5289490dd1c3bb86de4730a92261ae66ea8d44b79ed3cc26464f4c2cde581fbc"}, + {file = "coverage-7.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ca833941ec701fda15414be400c3259479bfde7ae6d806b69e63b3dc423b1832"}, + {file = "coverage-7.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd694e19c031733e446c8024dedd12a00cda87e1c10bd7b8539a87963685e969"}, + {file = "coverage-7.3.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aab8e9464c00da5cb9c536150b7fbcd8850d376d1151741dd0d16dfe1ba4fd26"}, + {file = "coverage-7.3.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87d38444efffd5b056fcc026c1e8d862191881143c3aa80bb11fcf9dca9ae204"}, + {file = "coverage-7.3.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8a07b692129b8a14ad7a37941a3029c291254feb7a4237f245cfae2de78de037"}, + {file = "coverage-7.3.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:2829c65c8faaf55b868ed7af3c7477b76b1c6ebeee99a28f59a2cb5907a45760"}, + {file = "coverage-7.3.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1f111a7d85658ea52ffad7084088277135ec5f368457275fc57f11cebb15607f"}, + {file = "coverage-7.3.1-cp312-cp312-win32.whl", hash = "sha256:c397c70cd20f6df7d2a52283857af622d5f23300c4ca8e5bd8c7a543825baa5a"}, + {file = "coverage-7.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:5ae4c6da8b3d123500f9525b50bf0168023313963e0e2e814badf9000dd6ef92"}, + {file = "coverage-7.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ca70466ca3a17460e8fc9cea7123c8cbef5ada4be3140a1ef8f7b63f2f37108f"}, + {file = "coverage-7.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f2781fd3cabc28278dc982a352f50c81c09a1a500cc2086dc4249853ea96b981"}, + {file = "coverage-7.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6407424621f40205bbe6325686417e5e552f6b2dba3535dd1f90afc88a61d465"}, + {file = "coverage-7.3.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:04312b036580ec505f2b77cbbdfb15137d5efdfade09156961f5277149f5e344"}, + {file = "coverage-7.3.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac9ad38204887349853d7c313f53a7b1c210ce138c73859e925bc4e5d8fc18e7"}, + {file = "coverage-7.3.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:53669b79f3d599da95a0afbef039ac0fadbb236532feb042c534fbb81b1a4e40"}, + {file = "coverage-7.3.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:614f1f98b84eb256e4f35e726bfe5ca82349f8dfa576faabf8a49ca09e630086"}, + {file = "coverage-7.3.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f1a317fdf5c122ad642db8a97964733ab7c3cf6009e1a8ae8821089993f175ff"}, + {file = "coverage-7.3.1-cp38-cp38-win32.whl", hash = "sha256:defbbb51121189722420a208957e26e49809feafca6afeef325df66c39c4fdb3"}, + {file = "coverage-7.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:f4f456590eefb6e1b3c9ea6328c1e9fa0f1006e7481179d749b3376fc793478e"}, + {file = "coverage-7.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f12d8b11a54f32688b165fd1a788c408f927b0960984b899be7e4c190ae758f1"}, + {file = "coverage-7.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f09195dda68d94a53123883de75bb97b0e35f5f6f9f3aa5bf6e496da718f0cb6"}, + {file = "coverage-7.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6601a60318f9c3945be6ea0f2a80571f4299b6801716f8a6e4846892737ebe4"}, + {file = "coverage-7.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07d156269718670d00a3b06db2288b48527fc5f36859425ff7cec07c6b367745"}, + {file = "coverage-7.3.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:636a8ac0b044cfeccae76a36f3b18264edcc810a76a49884b96dd744613ec0b7"}, + {file = "coverage-7.3.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5d991e13ad2ed3aced177f524e4d670f304c8233edad3210e02c465351f785a0"}, + {file = "coverage-7.3.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:586649ada7cf139445da386ab6f8ef00e6172f11a939fc3b2b7e7c9082052fa0"}, + {file = "coverage-7.3.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4aba512a15a3e1e4fdbfed2f5392ec221434a614cc68100ca99dcad7af29f3f8"}, + {file = "coverage-7.3.1-cp39-cp39-win32.whl", hash = "sha256:6bc6f3f4692d806831c136c5acad5ccedd0262aa44c087c46b7101c77e139140"}, + {file = "coverage-7.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:553d7094cb27db58ea91332e8b5681bac107e7242c23f7629ab1316ee73c4981"}, + {file = "coverage-7.3.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:220eb51f5fb38dfdb7e5d54284ca4d0cd70ddac047d750111a68ab1798945194"}, + {file = "coverage-7.3.1.tar.gz", hash = "sha256:6cb7fe1581deb67b782c153136541e20901aa312ceedaf1467dcb35255787952"}, ] [package.extras] toml = ["tomli"] +[[package]] +name = "cryptography" +version = "41.0.3" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +optional = false +python-versions = ">=3.7" +files = [ + {file = "cryptography-41.0.3-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:652627a055cb52a84f8c448185922241dd5217443ca194d5739b44612c5e6507"}, + {file = "cryptography-41.0.3-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:8f09daa483aedea50d249ef98ed500569841d6498aa9c9f4b0531b9964658922"}, + {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fd871184321100fb400d759ad0cddddf284c4b696568204d281c902fc7b0d81"}, + {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84537453d57f55a50a5b6835622ee405816999a7113267739a1b4581f83535bd"}, + {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:3fb248989b6363906827284cd20cca63bb1a757e0a2864d4c1682a985e3dca47"}, + {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:42cb413e01a5d36da9929baa9d70ca90d90b969269e5a12d39c1e0d475010116"}, + {file = "cryptography-41.0.3-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:aeb57c421b34af8f9fe830e1955bf493a86a7996cc1338fe41b30047d16e962c"}, + {file = "cryptography-41.0.3-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:6af1c6387c531cd364b72c28daa29232162010d952ceb7e5ca8e2827526aceae"}, + {file = "cryptography-41.0.3-cp37-abi3-win32.whl", hash = "sha256:0d09fb5356f975974dbcb595ad2d178305e5050656affb7890a1583f5e02a306"}, + {file = "cryptography-41.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:a983e441a00a9d57a4d7c91b3116a37ae602907a7618b882c8013b5762e80574"}, + {file = "cryptography-41.0.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5259cb659aa43005eb55a0e4ff2c825ca111a0da1814202c64d28a985d33b087"}, + {file = "cryptography-41.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:67e120e9a577c64fe1f611e53b30b3e69744e5910ff3b6e97e935aeb96005858"}, + {file = "cryptography-41.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:7efe8041897fe7a50863e51b77789b657a133c75c3b094e51b5e4b5cec7bf906"}, + {file = "cryptography-41.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ce785cf81a7bdade534297ef9e490ddff800d956625020ab2ec2780a556c313e"}, + {file = "cryptography-41.0.3-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:57a51b89f954f216a81c9d057bf1a24e2f36e764a1ca9a501a6964eb4a6800dd"}, + {file = "cryptography-41.0.3-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:4c2f0d35703d61002a2bbdcf15548ebb701cfdd83cdc12471d2bae80878a4207"}, + {file = "cryptography-41.0.3-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:23c2d778cf829f7d0ae180600b17e9fceea3c2ef8b31a99e3c694cbbf3a24b84"}, + {file = "cryptography-41.0.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:95dd7f261bb76948b52a5330ba5202b91a26fbac13ad0e9fc8a3ac04752058c7"}, + {file = "cryptography-41.0.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:41d7aa7cdfded09b3d73a47f429c298e80796c8e825ddfadc84c8a7f12df212d"}, + {file = "cryptography-41.0.3-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d0d651aa754ef58d75cec6edfbd21259d93810b73f6ec246436a21b7841908de"}, + {file = "cryptography-41.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:ab8de0d091acbf778f74286f4989cf3d1528336af1b59f3e5d2ebca8b5fe49e1"}, + {file = "cryptography-41.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a74fbcdb2a0d46fe00504f571a2a540532f4c188e6ccf26f1f178480117b33c4"}, + {file = "cryptography-41.0.3.tar.gz", hash = "sha256:6d192741113ef5e30d89dcb5b956ef4e1578f304708701b8b73d38e3e1461f34"}, +] + +[package.dependencies] +cffi = ">=1.12" + +[package.extras] +docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] +docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] +nox = ["nox"] +pep8test = ["black", "check-sdist", "mypy", "ruff"] +sdist = ["build"] +ssh = ["bcrypt (>=3.1.5)"] +test = ["pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test-randomorder = ["pytest-randomly"] + [[package]] name = "cytoolz" version = "0.12.2" @@ -840,6 +885,23 @@ files = [ {file = "deepmerge-0.3.0.tar.gz", hash = "sha256:f6fd7f1293c535fb599e197e750dbe8674503c5d2a89759b3c72a3c46746d4fd"}, ] +[[package]] +name = "deprecated" +version = "1.2.14" +description = "Python @deprecated decorator to deprecate old python classes, functions or methods." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "Deprecated-1.2.14-py2.py3-none-any.whl", hash = "sha256:6fac8b097794a90302bdbb17b9b815e732d3c4720583ff1b198499d78470466c"}, + {file = "Deprecated-1.2.14.tar.gz", hash = "sha256:e5323eb936458dccc2582dc6f9c322c852a775a27065ff2b0c4970b9d53d01b3"}, +] + +[package.dependencies] +wrapt = ">=1.10,<2" + +[package.extras] +dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "sphinx (<2)", "tox"] + [[package]] name = "distlib" version = "0.3.7" @@ -1219,6 +1281,20 @@ decorator = "*" ply = "*" six = "*" +[[package]] +name = "jwcrypto" +version = "1.5.0" +description = "Implementation of JOSE Web standards" +optional = false +python-versions = ">= 3.6" +files = [ + {file = "jwcrypto-1.5.0.tar.gz", hash = "sha256:2c1dc51cf8e38ddf324795dfe9426dee9dd46caf47f535ccbc18781fba810b8d"}, +] + +[package.dependencies] +cryptography = ">=3.4" +deprecated = "*" + [[package]] name = "lxml" version = "4.9.3" @@ -2123,13 +2199,13 @@ tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"] [[package]] name = "pytest" -version = "7.4.1" +version = "7.4.2" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-7.4.1-py3-none-any.whl", hash = "sha256:460c9a59b14e27c602eb5ece2e47bec99dc5fc5f6513cf924a7d03a578991b1f"}, - {file = "pytest-7.4.1.tar.gz", hash = "sha256:2f2301e797521b23e4d2585a0a3d7b5e50fdddaaf7e7d6773ea26ddb17c213ab"}, + {file = "pytest-7.4.2-py3-none-any.whl", hash = "sha256:1d881c6124e08ff0a1bb75ba3ec0bfd8b5354a01c194ddd5a0a870a48d99b002"}, + {file = "pytest-7.4.2.tar.gz", hash = "sha256:a766259cfab564a2ad52cb1aae1b881a75c3eb7e34ca3779697c23ed47c47069"}, ] [package.dependencies] @@ -2415,21 +2491,40 @@ files = [ {file = "ruff-0.0.285.tar.gz", hash = "sha256:45866048d1dcdcc80855998cb26c4b2b05881f9e043d2e3bfe1aa36d9a2e8f28"}, ] +[[package]] +name = "sd-jwt" +version = "0.9.1" +description = "The reference implementation of the IETF SD-JWT specification." +optional = false +python-versions = "^3.8" +files = [] +develop = false + +[package.dependencies] +jwcrypto = ">=1.3.1" +pyyaml = ">=5.4" + +[package.source] +type = "git" +url = "https://github.com/openwallet-foundation-labs/sd-jwt-python.git" +reference = "HEAD" +resolved_reference = "0d857bf9c4971d27f1e716814ba8b55d323caaaf" + [[package]] name = "setuptools" -version = "68.1.2" +version = "68.2.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-68.1.2-py3-none-any.whl", hash = "sha256:3d8083eed2d13afc9426f227b24fd1659489ec107c0e86cec2ffdde5c92e790b"}, - {file = "setuptools-68.1.2.tar.gz", hash = "sha256:3d4dfa6d95f1b101d695a6160a7626e15583af71a5f52176efa5d39a054d475d"}, + {file = "setuptools-68.2.0-py3-none-any.whl", hash = "sha256:af3d5949030c3f493f550876b2fd1dd5ec66689c4ee5d5344f009746f71fd5a8"}, + {file = "setuptools-68.2.0.tar.gz", hash = "sha256:00478ca80aeebeecb2f288d3206b0de568df5cd2b8fada1209843cc9a8d88a48"}, ] [package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5,<=7.1.2)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "simplejson" @@ -2791,6 +2886,90 @@ frameworks = ["Django (>=1.11.16)", "Flask (>=0.12.2)", "aiohttp (>=3.0.0)", "bo lint = ["flake8 (==3.7.8)", "flake8-bugbear (==19.8.0)", "mypy (==0.730)", "pre-commit (>=1.17,<2.0)"] tests = ["Django (>=1.11.16)", "Flask (>=0.12.2)", "aiohttp (>=3.0.0)", "bottle (>=0.12.13)", "falcon (>=1.4.0,<2.0)", "mock", "pyramid (>=1.9.1)", "pytest", "pytest-aiohttp (>=0.3.0)", "tornado (>=4.5.2)", "webapp2 (>=3.0.0b1)", "webtest (==2.0.33)", "webtest-aiohttp (==2.0.0)"] +[[package]] +name = "wrapt" +version = "1.15.0" +description = "Module for decorators, wrappers and monkey patching." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +files = [ + {file = "wrapt-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ca1cccf838cd28d5a0883b342474c630ac48cac5df0ee6eacc9c7290f76b11c1"}, + {file = "wrapt-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e826aadda3cae59295b95343db8f3d965fb31059da7de01ee8d1c40a60398b29"}, + {file = "wrapt-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5fc8e02f5984a55d2c653f5fea93531e9836abbd84342c1d1e17abc4a15084c2"}, + {file = "wrapt-1.15.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:96e25c8603a155559231c19c0349245eeb4ac0096fe3c1d0be5c47e075bd4f46"}, + {file = "wrapt-1.15.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:40737a081d7497efea35ab9304b829b857f21558acfc7b3272f908d33b0d9d4c"}, + {file = "wrapt-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:f87ec75864c37c4c6cb908d282e1969e79763e0d9becdfe9fe5473b7bb1e5f09"}, + {file = "wrapt-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:1286eb30261894e4c70d124d44b7fd07825340869945c79d05bda53a40caa079"}, + {file = "wrapt-1.15.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:493d389a2b63c88ad56cdc35d0fa5752daac56ca755805b1b0c530f785767d5e"}, + {file = "wrapt-1.15.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:58d7a75d731e8c63614222bcb21dd992b4ab01a399f1f09dd82af17bbfc2368a"}, + {file = "wrapt-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:21f6d9a0d5b3a207cdf7acf8e58d7d13d463e639f0c7e01d82cdb671e6cb7923"}, + {file = "wrapt-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ce42618f67741d4697684e501ef02f29e758a123aa2d669e2d964ff734ee00ee"}, + {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41d07d029dd4157ae27beab04d22b8e261eddfc6ecd64ff7000b10dc8b3a5727"}, + {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:54accd4b8bc202966bafafd16e69da9d5640ff92389d33d28555c5fd4f25ccb7"}, + {file = "wrapt-1.15.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fbfbca668dd15b744418265a9607baa970c347eefd0db6a518aaf0cfbd153c0"}, + {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:76e9c727a874b4856d11a32fb0b389afc61ce8aaf281ada613713ddeadd1cfec"}, + {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e20076a211cd6f9b44a6be58f7eeafa7ab5720eb796975d0c03f05b47d89eb90"}, + {file = "wrapt-1.15.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a74d56552ddbde46c246b5b89199cb3fd182f9c346c784e1a93e4dc3f5ec9975"}, + {file = "wrapt-1.15.0-cp310-cp310-win32.whl", hash = "sha256:26458da5653aa5b3d8dc8b24192f574a58984c749401f98fff994d41d3f08da1"}, + {file = "wrapt-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:75760a47c06b5974aa5e01949bf7e66d2af4d08cb8c1d6516af5e39595397f5e"}, + {file = "wrapt-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ba1711cda2d30634a7e452fc79eabcadaffedf241ff206db2ee93dd2c89a60e7"}, + {file = "wrapt-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:56374914b132c702aa9aa9959c550004b8847148f95e1b824772d453ac204a72"}, + {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a89ce3fd220ff144bd9d54da333ec0de0399b52c9ac3d2ce34b569cf1a5748fb"}, + {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3bbe623731d03b186b3d6b0d6f51865bf598587c38d6f7b0be2e27414f7f214e"}, + {file = "wrapt-1.15.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3abbe948c3cbde2689370a262a8d04e32ec2dd4f27103669a45c6929bcdbfe7c"}, + {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b67b819628e3b748fd3c2192c15fb951f549d0f47c0449af0764d7647302fda3"}, + {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7eebcdbe3677e58dd4c0e03b4f2cfa346ed4049687d839adad68cc38bb559c92"}, + {file = "wrapt-1.15.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:74934ebd71950e3db69960a7da29204f89624dde411afbfb3b4858c1409b1e98"}, + {file = "wrapt-1.15.0-cp311-cp311-win32.whl", hash = "sha256:bd84395aab8e4d36263cd1b9308cd504f6cf713b7d6d3ce25ea55670baec5416"}, + {file = "wrapt-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:a487f72a25904e2b4bbc0817ce7a8de94363bd7e79890510174da9d901c38705"}, + {file = "wrapt-1.15.0-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:4ff0d20f2e670800d3ed2b220d40984162089a6e2c9646fdb09b85e6f9a8fc29"}, + {file = "wrapt-1.15.0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9ed6aa0726b9b60911f4aed8ec5b8dd7bf3491476015819f56473ffaef8959bd"}, + {file = "wrapt-1.15.0-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:896689fddba4f23ef7c718279e42f8834041a21342d95e56922e1c10c0cc7afb"}, + {file = "wrapt-1.15.0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:75669d77bb2c071333417617a235324a1618dba66f82a750362eccbe5b61d248"}, + {file = "wrapt-1.15.0-cp35-cp35m-win32.whl", hash = "sha256:fbec11614dba0424ca72f4e8ba3c420dba07b4a7c206c8c8e4e73f2e98f4c559"}, + {file = "wrapt-1.15.0-cp35-cp35m-win_amd64.whl", hash = "sha256:fd69666217b62fa5d7c6aa88e507493a34dec4fa20c5bd925e4bc12fce586639"}, + {file = "wrapt-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b0724f05c396b0a4c36a3226c31648385deb6a65d8992644c12a4963c70326ba"}, + {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbeccb1aa40ab88cd29e6c7d8585582c99548f55f9b2581dfc5ba68c59a85752"}, + {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38adf7198f8f154502883242f9fe7333ab05a5b02de7d83aa2d88ea621f13364"}, + {file = "wrapt-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:578383d740457fa790fdf85e6d346fda1416a40549fe8db08e5e9bd281c6a475"}, + {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:a4cbb9ff5795cd66f0066bdf5947f170f5d63a9274f99bdbca02fd973adcf2a8"}, + {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:af5bd9ccb188f6a5fdda9f1f09d9f4c86cc8a539bd48a0bfdc97723970348418"}, + {file = "wrapt-1.15.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b56d5519e470d3f2fe4aa7585f0632b060d532d0696c5bdfb5e8319e1d0f69a2"}, + {file = "wrapt-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:77d4c1b881076c3ba173484dfa53d3582c1c8ff1f914c6461ab70c8428b796c1"}, + {file = "wrapt-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:077ff0d1f9d9e4ce6476c1a924a3332452c1406e59d90a2cf24aeb29eeac9420"}, + {file = "wrapt-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5c5aa28df055697d7c37d2099a7bc09f559d5053c3349b1ad0c39000e611d317"}, + {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3a8564f283394634a7a7054b7983e47dbf39c07712d7b177b37e03f2467a024e"}, + {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780c82a41dc493b62fc5884fb1d3a3b81106642c5c5c78d6a0d4cbe96d62ba7e"}, + {file = "wrapt-1.15.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e169e957c33576f47e21864cf3fc9ff47c223a4ebca8960079b8bd36cb014fd0"}, + {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b02f21c1e2074943312d03d243ac4388319f2456576b2c6023041c4d57cd7019"}, + {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f2e69b3ed24544b0d3dbe2c5c0ba5153ce50dcebb576fdc4696d52aa22db6034"}, + {file = "wrapt-1.15.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d787272ed958a05b2c86311d3a4135d3c2aeea4fc655705f074130aa57d71653"}, + {file = "wrapt-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:02fce1852f755f44f95af51f69d22e45080102e9d00258053b79367d07af39c0"}, + {file = "wrapt-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:abd52a09d03adf9c763d706df707c343293d5d106aea53483e0ec8d9e310ad5e"}, + {file = "wrapt-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cdb4f085756c96a3af04e6eca7f08b1345e94b53af8921b25c72f096e704e145"}, + {file = "wrapt-1.15.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:230ae493696a371f1dbffaad3dafbb742a4d27a0afd2b1aecebe52b740167e7f"}, + {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63424c681923b9f3bfbc5e3205aafe790904053d42ddcc08542181a30a7a51bd"}, + {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6bcbfc99f55655c3d93feb7ef3800bd5bbe963a755687cbf1f490a71fb7794b"}, + {file = "wrapt-1.15.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c99f4309f5145b93eca6e35ac1a988f0dc0a7ccf9ccdcd78d3c0adf57224e62f"}, + {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b130fe77361d6771ecf5a219d8e0817d61b236b7d8b37cc045172e574ed219e6"}, + {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:96177eb5645b1c6985f5c11d03fc2dbda9ad24ec0f3a46dcce91445747e15094"}, + {file = "wrapt-1.15.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5fe3e099cf07d0fb5a1e23d399e5d4d1ca3e6dfcbe5c8570ccff3e9208274f7"}, + {file = "wrapt-1.15.0-cp38-cp38-win32.whl", hash = "sha256:abd8f36c99512755b8456047b7be10372fca271bf1467a1caa88db991e7c421b"}, + {file = "wrapt-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:b06fa97478a5f478fb05e1980980a7cdf2712015493b44d0c87606c1513ed5b1"}, + {file = "wrapt-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2e51de54d4fb8fb50d6ee8327f9828306a959ae394d3e01a1ba8b2f937747d86"}, + {file = "wrapt-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0970ddb69bba00670e58955f8019bec4a42d1785db3faa043c33d81de2bf843c"}, + {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76407ab327158c510f44ded207e2f76b657303e17cb7a572ffe2f5a8a48aa04d"}, + {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cd525e0e52a5ff16653a3fc9e3dd827981917d34996600bbc34c05d048ca35cc"}, + {file = "wrapt-1.15.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d37ac69edc5614b90516807de32d08cb8e7b12260a285ee330955604ed9dd29"}, + {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:078e2a1a86544e644a68422f881c48b84fef6d18f8c7a957ffd3f2e0a74a0d4a"}, + {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2cf56d0e237280baed46f0b5316661da892565ff58309d4d2ed7dba763d984b8"}, + {file = "wrapt-1.15.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7dc0713bf81287a00516ef43137273b23ee414fe41a3c14be10dd95ed98a2df9"}, + {file = "wrapt-1.15.0-cp39-cp39-win32.whl", hash = "sha256:46ed616d5fb42f98630ed70c3529541408166c22cdfd4540b88d5f21006b0eff"}, + {file = "wrapt-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:eef4d64c650f33347c1f9266fa5ae001440b232ad9b98f1f43dfe7a79435c0a6"}, + {file = "wrapt-1.15.0-py3-none-any.whl", hash = "sha256:64b1df0f83706b4ef4cfb4fb0e4c2669100fd7ecacfb59e091fad300d4e04640"}, + {file = "wrapt-1.15.0.tar.gz", hash = "sha256:d06730c6aed78cee4126234cf2d071e01b44b915e725a6cb439a879ec9754a3a"}, +] + [[package]] name = "yarl" version = "1.9.2" @@ -2886,4 +3065,8 @@ indy = ["python3-indy"] [metadata] lock-version = "2.0" python-versions = "^3.9" +<<<<<<< HEAD content-hash = "3572d33f01ad13410e0ae03d925d0ab238b66e7c888ccfb29501f72f9bec66e7" +======= +content-hash = "cdc19dba91e323e22647314394ff3e805b87378762d9ac364cfd871d78604caf" +>>>>>>> feat: add sd-jwt python library to pyproject.toml diff --git a/pyproject.toml b/pyproject.toml index a634cc0ba7..ecf725499d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,6 +51,7 @@ unflatten="~0.1" asyncpg = ">=0.25.0,<0.26.0" web-py = ">=0.62,<1.0" pygments = ">=2.10,<3.0" +sd_jwt = {git = "https://github.com/openwallet-foundation-labs/sd-jwt-python.git"} # askar aries-askar= { version = "~0.2.5", optional = true } From 9acac83e0cc07d904a3cfea4f409a91c55457225 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Fri, 8 Sep 2023 09:09:30 -0600 Subject: [PATCH 59/85] chore: ruff Signed-off-by: Char Howland --- aries_cloudagent/wallet/routes.py | 6 ++---- aries_cloudagent/wallet/sd_jwt.py | 9 +++------ 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/aries_cloudagent/wallet/routes.py b/aries_cloudagent/wallet/routes.py index 3ae9b16e5c..22481b2d6a 100644 --- a/aries_cloudagent/wallet/routes.py +++ b/aries_cloudagent/wallet/routes.py @@ -990,8 +990,7 @@ async def wallet_jwt_sign(request: web.BaseRequest): @request_schema(SDJWSCreateSchema) @response_schema(WalletModuleResponseSchema(), description="") async def wallet_sd_jwt_sign(request: web.BaseRequest): - """ - Request handler for sd-jws creation using did. + """Request handler for sd-jws creation using did. Args: "headers": { ... }, @@ -1058,8 +1057,7 @@ async def wallet_jwt_verify(request: web.BaseRequest): @request_schema(SDJWSVerifySchema()) @response_schema(SDJWSVerifyResponseSchema(), 200, description="") async def wallet_sd_jwt_verify(request: web.BaseRequest): - """ - Request handler for sd-jws validation using did. + """Request handler for sd-jws validation using did. Args: "sd-jwt": { ... } diff --git a/aries_cloudagent/wallet/sd_jwt.py b/aries_cloudagent/wallet/sd_jwt.py index 817055e24f..142ff2ca07 100644 --- a/aries_cloudagent/wallet/sd_jwt.py +++ b/aries_cloudagent/wallet/sd_jwt.py @@ -98,8 +98,7 @@ def create_json_paths(it, current_path="", path_list=None) -> List: def sort_sd_list(sd_list) -> List: - """ - Sorts sd_list. + """Sorts sd_list. Ensures that selectively disclosable claims deepest in the structure are handled first. @@ -110,8 +109,7 @@ def sort_sd_list(sd_list) -> List: def separate_list_splices(non_sd_list) -> List: - """ - Separate list splices in the non_sd_list into individual indices. + """Separate list splices in the non_sd_list into individual indices. This is necessary in order to properly construct the inverse of the claims which should not be selectively disclosable in the case @@ -145,8 +143,7 @@ async def sd_jwt_sign( did: Optional[str] = None, verification_method: Optional[str] = None, ) -> str: - """ - Sign sd-jwt. + """Sign sd-jwt. Use non_sd_list and json paths for payload elements to create a list of claims that can be selectively disclosable. Use this list to wrap From 1b8bc8b943df9f64f4133d1a0ac43d853b673d3c Mon Sep 17 00:00:00 2001 From: Char Howland Date: Fri, 8 Sep 2023 14:27:20 -0600 Subject: [PATCH 60/85] fix: move fixtures to conftest.py Signed-off-by: Char Howland --- aries_cloudagent/wallet/tests/conftest.py | 66 ++++++++++++++++++++ aries_cloudagent/wallet/tests/test_jwt.py | 65 +------------------ aries_cloudagent/wallet/tests/test_sd_jwt.py | 4 -- 3 files changed, 67 insertions(+), 68 deletions(-) create mode 100644 aries_cloudagent/wallet/tests/conftest.py diff --git a/aries_cloudagent/wallet/tests/conftest.py b/aries_cloudagent/wallet/tests/conftest.py new file mode 100644 index 0000000000..eed9e1aef7 --- /dev/null +++ b/aries_cloudagent/wallet/tests/conftest.py @@ -0,0 +1,66 @@ +import pytest +from aries_cloudagent.resolver.did_resolver import DIDResolver +from aries_cloudagent.resolver.tests.test_did_resolver import MockResolver +from aries_cloudagent.wallet.default_verification_key_strategy import ( + BaseVerificationKeyStrategy, + DefaultVerificationKeyStrategy, +) + +from ...core.in_memory.profile import InMemoryProfile +from ...wallet.did_method import DIDMethods +from ...wallet.in_memory import InMemoryWallet + + +@pytest.fixture() +async def profile(): + """In memory profile with injected dependencies.""" + + mock_sov = MockResolver( + ["key"], + resolved={ + "@context": "https://www.w3.org/ns/did/v1", + "id": "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL", + "verificationMethod": [ + { + "id": "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL", + "type": "Ed25519VerificationKey2018", + "controller": "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL", + "publicKeyBase58": "3Dn1SJNPaCXcvvJvSbsFWP2xaCjMom3can8CQNhWrTRx", + } + ], + "authentication": [ + "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL" + ], + "assertionMethod": [ + "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL" + ], + "capabilityDelegation": [ + "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL" + ], + "capabilityInvocation": [ + "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL" + ], + "keyAgreement": [ + { + "id": "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6LSbkodSr6SU2trs8VUgnrnWtSm7BAPG245ggrBmSrxbv1R", + "type": "X25519KeyAgreementKey2019", + "controller": "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL", + "publicKeyBase58": "5dTvYHaNaB7mk7iA9LqCJEHG2dGZQsvoi8WGzDRtYEf", + } + ], + }, + native=True, + ) + yield InMemoryProfile.test_profile( + bind={ + DIDMethods: DIDMethods(), + BaseVerificationKeyStrategy: DefaultVerificationKeyStrategy(), + DIDResolver: DIDResolver([mock_sov]), + } + ) + + +@pytest.fixture() +async def in_memory_wallet(profile): + """In memory wallet for testing.""" + yield InMemoryWallet(profile) diff --git a/aries_cloudagent/wallet/tests/test_jwt.py b/aries_cloudagent/wallet/tests/test_jwt.py index 289c65901f..b3a2b41f32 100644 --- a/aries_cloudagent/wallet/tests/test_jwt.py +++ b/aries_cloudagent/wallet/tests/test_jwt.py @@ -1,75 +1,12 @@ import pytest -from aries_cloudagent.resolver.did_resolver import DIDResolver -from aries_cloudagent.resolver.tests.test_did_resolver import MockResolver -from aries_cloudagent.wallet.default_verification_key_strategy import ( - BaseVerificationKeyStrategy, - DefaultVerificationKeyStrategy, -) from aries_cloudagent.wallet.key_type import ED25519 -from ...core.in_memory.profile import InMemoryProfile -from ...wallet.did_method import KEY, DIDMethods -from ...wallet.in_memory import InMemoryWallet +from ...wallet.did_method import KEY from ..jwt import jwt_sign, jwt_verify, resolve_public_key_by_kid_for_verify -@pytest.fixture() -async def profile(): - """In memory profile with injected dependencies.""" - - mock_sov = MockResolver( - ["key"], - resolved={ - "@context": "https://www.w3.org/ns/did/v1", - "id": "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL", - "verificationMethod": [ - { - "id": "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL", - "type": "Ed25519VerificationKey2018", - "controller": "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL", - "publicKeyBase58": "3Dn1SJNPaCXcvvJvSbsFWP2xaCjMom3can8CQNhWrTRx", - } - ], - "authentication": [ - "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL" - ], - "assertionMethod": [ - "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL" - ], - "capabilityDelegation": [ - "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL" - ], - "capabilityInvocation": [ - "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL" - ], - "keyAgreement": [ - { - "id": "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6LSbkodSr6SU2trs8VUgnrnWtSm7BAPG245ggrBmSrxbv1R", - "type": "X25519KeyAgreementKey2019", - "controller": "did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL", - "publicKeyBase58": "5dTvYHaNaB7mk7iA9LqCJEHG2dGZQsvoi8WGzDRtYEf", - } - ], - }, - native=True, - ) - yield InMemoryProfile.test_profile( - bind={ - DIDMethods: DIDMethods(), - BaseVerificationKeyStrategy: DefaultVerificationKeyStrategy(), - DIDResolver: DIDResolver([mock_sov]), - } - ) - - -@pytest.fixture() -async def in_memory_wallet(profile): - """In memory wallet for testing.""" - yield InMemoryWallet(profile) - - class TestJWT: """Tests for JWT sign and verify using dids.""" diff --git a/aries_cloudagent/wallet/tests/test_sd_jwt.py b/aries_cloudagent/wallet/tests/test_sd_jwt.py index 8e0cf10137..9282c87b9c 100644 --- a/aries_cloudagent/wallet/tests/test_sd_jwt.py +++ b/aries_cloudagent/wallet/tests/test_sd_jwt.py @@ -2,15 +2,11 @@ import json import pytest - from ...wallet.did_method import KEY from ...wallet.key_type import ED25519 from ...wallet.jwt import jwt_sign - from ..sd_jwt import SDJWTVerifyResult, sd_jwt_sign, sd_jwt_verify -from .test_jwt import profile, in_memory_wallet - @pytest.fixture def create_address_payload(): From 11684e87153983f994a07f5f06fa25653f0b9bb0 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Fri, 8 Sep 2023 14:50:56 -0600 Subject: [PATCH 61/85] chore: poetry lock Signed-off-by: Char Howland --- poetry.lock | 184 +++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 155 insertions(+), 29 deletions(-) diff --git a/poetry.lock b/poetry.lock index d57e8e0298..f2cda674e2 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,9 +1,10 @@ -# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. +# This file is automatically @generated by Poetry and should not be changed by hand. [[package]] name = "aiohttp" version = "3.8.5" description = "Async http client/server framework (asyncio)" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -112,6 +113,7 @@ speedups = ["Brotli", "aiodns", "cchardet"] name = "aiohttp-apispec" version = "2.2.3" description = "Build and document REST APIs with aiohttp and apispec" +category = "main" optional = false python-versions = ">=3.5" files = [ @@ -128,6 +130,7 @@ webargs = "<6.0" name = "aiohttp-cors" version = "0.7.0" description = "CORS support for aiohttp" +category = "main" optional = false python-versions = "*" files = [ @@ -142,6 +145,7 @@ aiohttp = ">=1.1" name = "aioredis" version = "2.0.1" description = "asyncio (PEP 3156) Redis support" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -160,6 +164,7 @@ hiredis = ["hiredis (>=1.0)"] name = "aiosignal" version = "1.3.1" description = "aiosignal: a list of registered asynchronous callbacks" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -174,6 +179,7 @@ frozenlist = ">=1.1.0" name = "alabaster" version = "0.7.13" description = "A configurable sidebar-enabled Sphinx theme" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -185,6 +191,7 @@ files = [ name = "apispec" version = "3.3.2" description = "A pluggable API specification generator. Currently supports the OpenAPI Specification (f.k.a. the Swagger specification)." +category = "main" optional = false python-versions = ">=3.5" files = [ @@ -204,6 +211,7 @@ yaml = ["PyYAML (>=3.10)"] name = "aries-askar" version = "0.2.9" description = "" +category = "main" optional = true python-versions = ">=3.6.3" files = [ @@ -219,6 +227,7 @@ cached-property = ">=1.5,<2.0" name = "async-timeout" version = "4.0.3" description = "Timeout context manager for asyncio programs" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -230,6 +239,7 @@ files = [ name = "asyncpg" version = "0.25.0" description = "An asyncio PostgreSQL driver" +category = "main" optional = false python-versions = ">=3.6.0" files = [ @@ -270,6 +280,7 @@ test = ["flake8 (>=3.9.2,<3.10.0)", "pycodestyle (>=2.7.0,<2.8.0)", "uvloop (>=0 name = "asynctest" version = "0.13.0" description = "Enhance the standard unittest package with features for testing asyncio libraries" +category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -281,6 +292,7 @@ files = [ name = "attrs" version = "23.1.0" description = "Classes Without Boilerplate" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -299,6 +311,7 @@ tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pyte name = "babel" version = "2.12.1" description = "Internationalization utilities" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -310,6 +323,7 @@ files = [ name = "base58" version = "2.1.1" description = "Base58 and Base58Check implementation." +category = "main" optional = false python-versions = ">=3.5" files = [ @@ -324,6 +338,7 @@ tests = ["PyHamcrest (>=2.0.2)", "mypy", "pytest (>=4.6)", "pytest-benchmark", " name = "bases" version = "0.2.1" description = "Python library for general Base-N encodings." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -342,6 +357,7 @@ dev = ["base58", "mypy", "pylint", "pytest", "pytest-cov"] name = "black" version = "23.7.0" description = "The uncompromising code formatter." +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -388,6 +404,7 @@ uvloop = ["uvloop (>=0.15.2)"] name = "cached-property" version = "1.5.2" description = "A decorator for caching properties in classes." +category = "main" optional = true python-versions = "*" files = [ @@ -399,6 +416,7 @@ files = [ name = "cachetools" version = "5.3.1" description = "Extensible memoizing collections and decorators" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -410,6 +428,7 @@ files = [ name = "certifi" version = "2023.7.22" description = "Python package for providing Mozilla's CA Bundle." +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -421,6 +440,7 @@ files = [ name = "cffi" version = "1.15.1" description = "Foreign Function Interface for Python calling C code." +category = "main" optional = false python-versions = "*" files = [ @@ -497,6 +517,7 @@ pycparser = "*" name = "cfgv" version = "3.4.0" description = "Validate configuration and produce human readable error messages." +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -508,6 +529,7 @@ files = [ name = "charset-normalizer" version = "3.2.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -592,6 +614,7 @@ files = [ name = "cheroot" version = "10.0.0" description = "Highly-optimized, pure-python HTTP server" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -610,6 +633,7 @@ docs = ["furo", "jaraco.packaging (>=3.2)", "python-dateutil", "sphinx (>=1.8.2) name = "click" version = "8.1.7" description = "Composable command line interface toolkit" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -624,6 +648,7 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""} name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." +category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ @@ -635,6 +660,7 @@ files = [ name = "configargparse" version = "1.5.5" description = "A drop-in replacement for argparse that allows options to also be set via config files and/or environment variables." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -650,6 +676,7 @@ yaml = ["PyYAML"] name = "coverage" version = "7.3.1" description = "Code coverage measurement for Python" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -714,6 +741,7 @@ toml = ["tomli"] name = "cryptography" version = "41.0.3" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -759,6 +787,7 @@ test-randomorder = ["pytest-randomly"] name = "cytoolz" version = "0.12.2" description = "Cython implementation of Toolz: High performance functional utilities" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -867,6 +896,7 @@ cython = ["cython"] name = "decorator" version = "5.1.1" description = "Decorators for Humans" +category = "main" optional = false python-versions = ">=3.5" files = [ @@ -878,6 +908,7 @@ files = [ name = "deepmerge" version = "0.3.0" description = "a toolset to deeply merge python dictionaries." +category = "main" optional = false python-versions = ">=3" files = [ @@ -889,6 +920,7 @@ files = [ name = "deprecated" version = "1.2.14" description = "Python @deprecated decorator to deprecate old python classes, functions or methods." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -906,6 +938,7 @@ dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "sphinx (<2)", "tox"] name = "distlib" version = "0.3.7" description = "Distribution utilities" +category = "dev" optional = false python-versions = "*" files = [ @@ -915,19 +948,21 @@ files = [ [[package]] name = "docutils" -version = "0.20.1" +version = "0.18.1" description = "Docutils -- Python Documentation Utilities" +category = "dev" optional = false -python-versions = ">=3.7" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ - {file = "docutils-0.20.1-py3-none-any.whl", hash = "sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6"}, - {file = "docutils-0.20.1.tar.gz", hash = "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b"}, + {file = "docutils-0.18.1-py2.py3-none-any.whl", hash = "sha256:23010f129180089fbcd3bc08cfefccb3b890b0050e1ca00c867036e9d161b98c"}, + {file = "docutils-0.18.1.tar.gz", hash = "sha256:679987caf361a7539d76e584cbeddc311e3aee937877c87346f31debc63e9d06"}, ] [[package]] name = "ecdsa" version = "0.16.1" description = "ECDSA cryptographic signature library (pure python)" +category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -946,6 +981,7 @@ gmpy2 = ["gmpy2"] name = "eth-hash" version = "0.3.3" description = "eth-hash: The Ethereum hashing function, keccak256, sometimes (erroneously) called sha3" +category = "main" optional = false python-versions = ">=3.5, <4" files = [ @@ -965,6 +1001,7 @@ test = ["pytest (==5.4.1)", "pytest-xdist", "tox (==3.14.6)"] name = "eth-typing" version = "2.3.0" description = "eth-typing: Common type annotations for ethereum python packages" +category = "main" optional = false python-versions = ">=3.5, <4" files = [ @@ -982,6 +1019,7 @@ test = ["pytest (>=4.4,<4.5)", "pytest-xdist", "tox (>=2.9.1,<3)"] name = "eth-utils" version = "1.10.0" description = "eth-utils: Common utility functions for python code that interacts with Ethereum" +category = "main" optional = false python-versions = ">=3.5,!=3.5.2,<4" files = [ @@ -1005,6 +1043,7 @@ test = ["hypothesis (>=4.43.0,<5.0.0)", "pytest (==5.4.1)", "pytest-xdist", "tox name = "exceptiongroup" version = "1.1.3" description = "Backport of PEP 654 (exception groups)" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1019,6 +1058,7 @@ test = ["pytest (>=6)"] name = "filelock" version = "3.12.2" description = "A platform independent file lock." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1034,6 +1074,7 @@ testing = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "diff-cover (>=7.5)", "p name = "frozendict" version = "2.3.8" description = "A simple immutable dictionary" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1080,6 +1121,7 @@ files = [ name = "frozenlist" version = "1.4.0" description = "A list-like structure which implements collections.abc.MutableSequence" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1150,6 +1192,7 @@ files = [ name = "identify" version = "2.5.27" description = "File identification library for Python" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1164,6 +1207,7 @@ license = ["ukkonen"] name = "idna" version = "3.4" description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" optional = false python-versions = ">=3.5" files = [ @@ -1175,6 +1219,7 @@ files = [ name = "imagesize" version = "1.4.1" description = "Getting image size from png/jpeg/jpeg2000/gif file" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -1186,6 +1231,7 @@ files = [ name = "indy-credx" version = "1.0.0" description = "" +category = "main" optional = true python-versions = ">=3.6.3" files = [ @@ -1199,6 +1245,7 @@ files = [ name = "indy-vdr" version = "0.3.4" description = "" +category = "main" optional = true python-versions = ">=3.6.3" files = [ @@ -1211,6 +1258,7 @@ files = [ name = "inflection" version = "0.5.1" description = "A port of Ruby on Rails inflector to Python" +category = "main" optional = false python-versions = ">=3.5" files = [ @@ -1222,6 +1270,7 @@ files = [ name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1233,6 +1282,7 @@ files = [ name = "jaraco-functools" version = "3.9.0" description = "Functools like those found in stdlib" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1252,6 +1302,7 @@ testing = ["jaraco.classes", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-c name = "jinja2" version = "3.1.2" description = "A very fast and expressive template engine." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1269,6 +1320,7 @@ i18n = ["Babel (>=2.7)"] name = "jsonpath-ng" version = "1.5.2" description = "A final implementation of JSONPath for Python that aims to be standard compliant, including arithmetic and binary comparison operators and providing clear AST for metaprogramming." +category = "main" optional = false python-versions = "*" files = [ @@ -1285,6 +1337,7 @@ six = "*" name = "jwcrypto" version = "1.5.0" description = "Implementation of JOSE Web standards" +category = "main" optional = false python-versions = ">= 3.6" files = [ @@ -1299,6 +1352,7 @@ deprecated = "*" name = "lxml" version = "4.9.3" description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*" files = [ @@ -1406,6 +1460,7 @@ source = ["Cython (>=0.29.35)"] name = "markdown" version = "3.1.1" description = "Python implementation of Markdown." +category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" files = [ @@ -1423,6 +1478,7 @@ testing = ["coverage", "pyyaml"] name = "markupsafe" version = "2.0.1" description = "Safely add untrusted strings to HTML/XML markup." +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1501,6 +1557,7 @@ files = [ name = "marshmallow" version = "3.20.1" description = "A lightweight library for converting complex datatypes to and from native Python datatypes." +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1521,6 +1578,7 @@ tests = ["pytest", "pytz", "simplejson"] name = "mock" version = "4.0.3" description = "Rolling backport of unittest.mock for all Pythons" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1537,6 +1595,7 @@ test = ["pytest (<5.4)", "pytest-cov"] name = "more-itertools" version = "10.1.0" description = "More routines for operating on iterables, beyond itertools" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1548,6 +1607,7 @@ files = [ name = "msgpack" version = "1.0.5" description = "MessagePack serializer" +category = "main" optional = false python-versions = "*" files = [ @@ -1620,6 +1680,7 @@ files = [ name = "multidict" version = "6.0.4" description = "multidict implementation" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1703,6 +1764,7 @@ files = [ name = "multiformats" version = "0.2.1" description = "Python implementation of multiformats protocols." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1724,6 +1786,7 @@ full = ["blake3", "mmh3", "pycryptodomex", "pysha3", "pyskein"] name = "multiformats-config" version = "0.2.0.post4" description = "Pre-loading configuration module for the 'multiformats' package." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1741,6 +1804,7 @@ dev = ["mypy", "pylint", "pytest", "pytest-cov"] name = "mypy-extensions" version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." +category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -1752,6 +1816,7 @@ files = [ name = "nest-asyncio" version = "1.5.7" description = "Patch asyncio to allow nested event loops" +category = "main" optional = false python-versions = ">=3.5" files = [ @@ -1763,6 +1828,7 @@ files = [ name = "nodeenv" version = "1.8.0" description = "Node.js virtual environment builder" +category = "dev" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" files = [ @@ -1777,6 +1843,7 @@ setuptools = "*" name = "packaging" version = "23.1" description = "Core utilities for Python packages" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1788,6 +1855,7 @@ files = [ name = "pathspec" version = "0.11.2" description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1799,6 +1867,7 @@ files = [ name = "peerdid" version = "0.5.2" description = "PeerDID for Python" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1818,6 +1887,7 @@ tests = ["pytest (==6.2.5)", "pytest-xdist (==2.3.0)"] name = "pillow" version = "10.0.0" description = "Python Imaging Library (Fork)" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1887,6 +1957,7 @@ tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "pa name = "platformdirs" version = "3.10.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1902,6 +1973,7 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-co name = "pluggy" version = "1.3.0" description = "plugin and hook calling mechanisms for python" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1917,6 +1989,7 @@ testing = ["pytest", "pytest-benchmark"] name = "ply" version = "3.11" description = "Python Lex & Yacc" +category = "main" optional = false python-versions = "*" files = [ @@ -1928,6 +2001,7 @@ files = [ name = "portalocker" version = "2.7.0" description = "Wraps the portalocker recipe for easy usage" +category = "main" optional = false python-versions = ">=3.5" files = [ @@ -1947,6 +2021,7 @@ tests = ["pytest (>=5.4.1)", "pytest-cov (>=2.8.1)", "pytest-mypy (>=0.8.0)", "p name = "pre-commit" version = "3.3.3" description = "A framework for managing and maintaining multi-language pre-commit hooks." +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1965,6 +2040,7 @@ virtualenv = ">=20.10.0" name = "prompt-toolkit" version = "2.0.10" description = "Library for building powerful interactive command lines in Python" +category = "main" optional = false python-versions = ">=2.6,<3.0.0 || >=3.3.0" files = [ @@ -1981,6 +2057,7 @@ wcwidth = "*" name = "ptvsd" version = "4.3.2" description = "Remote debugging server for Python support in Visual Studio and Visual Studio Code" +category = "dev" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" files = [ @@ -2015,6 +2092,7 @@ files = [ name = "pycparser" version = "2.21" description = "C parser in Python" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -2026,6 +2104,7 @@ files = [ name = "pydantic" version = "1.9.2" description = "Data validation and settings management using python type hints" +category = "main" optional = false python-versions = ">=3.6.1" files = [ @@ -2077,6 +2156,7 @@ email = ["email-validator (>=1.0.3)"] name = "pydevd" version = "1.5.1" description = "PyDev.Debugger (used in PyDev, PyCharm and VSCode Python)" +category = "dev" optional = false python-versions = "*" files = [ @@ -2097,6 +2177,7 @@ files = [ name = "pydevd-pycharm" version = "193.6015.41" description = "PyCharm Debugger (used in PyCharm and PyDev)" +category = "dev" optional = false python-versions = "*" files = [ @@ -2107,6 +2188,7 @@ files = [ name = "pydid" version = "0.3.8" description = "Python library for validating, constructing, and representing DIDs and DID Documents" +category = "main" optional = false python-versions = ">=3.6.9,<4.0.0" files = [ @@ -2123,6 +2205,7 @@ typing-extensions = ">=4.0.0,<4.1.0" name = "pygments" version = "2.16.1" description = "Pygments is a syntax highlighting package written in Python." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2137,6 +2220,7 @@ plugins = ["importlib-metadata"] name = "pyjwt" version = "2.8.0" description = "JSON Web Token implementation in Python" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2154,6 +2238,7 @@ tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] name = "pyld" version = "2.0.3" description = "Python implementation of the JSON-LD API" +category = "main" optional = false python-versions = "*" files = [ @@ -2175,6 +2260,7 @@ requests = ["requests"] name = "pynacl" version = "1.5.0" description = "Python binding to the Networking and Cryptography (NaCl) library" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -2201,6 +2287,7 @@ tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"] name = "pytest" version = "7.4.2" description = "pytest: simple powerful testing with Python" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2223,6 +2310,7 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no name = "pytest-asyncio" version = "0.14.0" description = "Pytest support for asyncio." +category = "dev" optional = false python-versions = ">= 3.5" files = [ @@ -2240,6 +2328,7 @@ testing = ["async-generator (>=1.3)", "coverage", "hypothesis (>=5.7.1)"] name = "pytest-cov" version = "2.10.1" description = "Pytest plugin for measuring coverage." +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -2258,6 +2347,7 @@ testing = ["fields", "hunter", "process-tests (==2.0.2)", "pytest-xdist", "six", name = "pytest-ruff" version = "0.1.1" description = "pytest plugin to check ruff requirements." +category = "dev" optional = false python-versions = ">=3.7,<4.0" files = [ @@ -2272,6 +2362,7 @@ ruff = ">=0.0.242" name = "python-dateutil" version = "2.8.2" description = "Extensions to the standard Python datetime module" +category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ @@ -2286,6 +2377,7 @@ six = ">=1.5" name = "python-json-logger" version = "2.0.7" description = "A python library adding a json log formatter" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -2297,6 +2389,7 @@ files = [ name = "python3-indy" version = "1.16.0.post286" description = "This is the official SDK for Hyperledger Indy (https://www.hyperledger.org/projects), which provides a distributed-ledger-based foundation for self-sovereign identity (https://sovrin.org). The major artifact of the SDK is a c-callable library." +category = "main" optional = true python-versions = "*" files = [ @@ -2313,6 +2406,7 @@ test = ["base58", "pytest (<3.7)", "pytest-asyncio (==0.10.0)"] name = "pytz" version = "2021.1" description = "World timezone definitions, modern and historical" +category = "main" optional = false python-versions = "*" files = [ @@ -2324,6 +2418,7 @@ files = [ name = "pywin32" version = "306" description = "Python for Window Extensions" +category = "main" optional = false python-versions = "*" files = [ @@ -2347,6 +2442,7 @@ files = [ name = "pyyaml" version = "6.0.1" description = "YAML parser and emitter for Python" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -2355,7 +2451,6 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, - {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -2363,15 +2458,8 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, - {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, - {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, - {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, - {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -2388,7 +2476,6 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, - {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -2396,7 +2483,6 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, - {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -2406,6 +2492,7 @@ files = [ name = "qrcode" version = "6.1" description = "QR Code image generator" +category = "main" optional = false python-versions = "*" files = [ @@ -2428,6 +2515,7 @@ test = ["mock", "pytest", "pytest-cov"] name = "requests" version = "2.31.0" description = "Python HTTP for Humans." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2449,6 +2537,7 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] name = "rlp" version = "1.2.0" description = "A package for Recursive Length Prefix encoding and decoding" +category = "main" optional = false python-versions = "*" files = [ @@ -2469,6 +2558,7 @@ test = ["hypothesis (==3.56.5)", "pytest (==3.3.2)", "tox (>=2.9.1,<3)"] name = "ruff" version = "0.0.285" description = "An extremely fast Python linter, written in Rust." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2495,6 +2585,7 @@ files = [ name = "sd-jwt" version = "0.9.1" description = "The reference implementation of the IETF SD-JWT specification." +category = "main" optional = false python-versions = "^3.8" files = [] @@ -2514,6 +2605,7 @@ resolved_reference = "0d857bf9c4971d27f1e716814ba8b55d323caaaf" name = "setuptools" version = "68.2.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -2530,6 +2622,7 @@ testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs ( name = "simplejson" version = "3.19.1" description = "Simple, fast, extensible JSON encoder/decoder for Python" +category = "main" optional = false python-versions = ">=2.5, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -2624,6 +2717,7 @@ files = [ name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -2635,6 +2729,7 @@ files = [ name = "snowballstemmer" version = "2.2.0" description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." +category = "dev" optional = false python-versions = "*" files = [ @@ -2646,6 +2741,7 @@ files = [ name = "sphinx" version = "1.8.4" description = "Python documentation generator" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -2674,25 +2770,44 @@ websupport = ["sqlalchemy (>=0.9)", "whoosh (>=2.0)"] [[package]] name = "sphinx-rtd-theme" -version = "0.5.1" +version = "1.3.0" description = "Read the Docs theme for Sphinx" +category = "dev" optional = false -python-versions = "*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ - {file = "sphinx_rtd_theme-0.5.1-py2.py3-none-any.whl", hash = "sha256:fa6bebd5ab9a73da8e102509a86f3fcc36dec04a0b52ea80e5a033b2aba00113"}, - {file = "sphinx_rtd_theme-0.5.1.tar.gz", hash = "sha256:eda689eda0c7301a80cf122dad28b1861e5605cbf455558f3775e1e8200e83a5"}, + {file = "sphinx_rtd_theme-1.3.0-py2.py3-none-any.whl", hash = "sha256:46ddef89cc2416a81ecfbeaceab1881948c014b1b6e4450b815311a89fb977b0"}, + {file = "sphinx_rtd_theme-1.3.0.tar.gz", hash = "sha256:590b030c7abb9cf038ec053b95e5380b5c70d61591eb0b552063fbe7c41f0931"}, ] [package.dependencies] -sphinx = "*" +docutils = "<0.19" +sphinx = ">=1.6,<8" +sphinxcontrib-jquery = ">=4,<5" [package.extras] -dev = ["bump2version", "sphinxcontrib-httpdomain", "transifex-client"] +dev = ["bump2version", "sphinxcontrib-httpdomain", "transifex-client", "wheel"] + +[[package]] +name = "sphinxcontrib-jquery" +version = "4.1" +description = "Extension to include jQuery on newer Sphinx releases" +category = "dev" +optional = false +python-versions = ">=2.7" +files = [ + {file = "sphinxcontrib-jquery-4.1.tar.gz", hash = "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a"}, + {file = "sphinxcontrib_jquery-4.1-py2.py3-none-any.whl", hash = "sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae"}, +] + +[package.dependencies] +Sphinx = ">=1.8" [[package]] name = "sphinxcontrib-serializinghtml" version = "1.1.5" description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." +category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -2708,6 +2823,7 @@ test = ["pytest"] name = "sphinxcontrib-websupport" version = "1.2.4" description = "Sphinx API for Web Apps" +category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -2726,6 +2842,7 @@ test = ["Sphinx", "pytest", "sqlalchemy", "whoosh"] name = "tomli" version = "2.0.1" description = "A lil' TOML parser" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2737,6 +2854,7 @@ files = [ name = "toolz" version = "0.12.0" description = "List processing tools and functional utilities" +category = "main" optional = false python-versions = ">=3.5" files = [ @@ -2748,6 +2866,7 @@ files = [ name = "typing-extensions" version = "4.0.1" description = "Backported and Experimental Type Hints for Python 3.6+" +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -2759,6 +2878,7 @@ files = [ name = "typing-validation" version = "1.0.0.post2" description = "A simple library for runtime type-checking." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2773,6 +2893,7 @@ dev = ["mypy", "pylint", "pytest", "pytest-cov", "rich"] name = "unflatten" version = "0.1.1" description = "Unflatten dict to dict with nested dict/arrays" +category = "main" optional = false python-versions = "*" files = [ @@ -2784,6 +2905,7 @@ files = [ name = "urllib3" version = "2.0.4" description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2801,6 +2923,7 @@ zstd = ["zstandard (>=0.18.0)"] name = "ursa-bbs-signatures" version = "1.0.1" description = "" +category = "main" optional = true python-versions = ">=3.6.3" files = [ @@ -2813,6 +2936,7 @@ files = [ name = "varint" version = "1.0.2" description = "Simple python varint implementation" +category = "main" optional = false python-versions = "*" files = [ @@ -2821,13 +2945,14 @@ files = [ [[package]] name = "virtualenv" -version = "20.24.4" +version = "20.24.5" description = "Virtual Python Environment builder" +category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "virtualenv-20.24.4-py3-none-any.whl", hash = "sha256:29c70bb9b88510f6414ac3e55c8b413a1f96239b6b789ca123437d5e892190cb"}, - {file = "virtualenv-20.24.4.tar.gz", hash = "sha256:772b05bfda7ed3b8ecd16021ca9716273ad9f4467c801f27e83ac73430246dca"}, + {file = "virtualenv-20.24.5-py3-none-any.whl", hash = "sha256:b80039f280f4919c77b30f1c23294ae357c4c8701042086e3fc005963e4e537b"}, + {file = "virtualenv-20.24.5.tar.gz", hash = "sha256:e8361967f6da6fbdf1426483bfe9fca8287c242ac0bc30429905721cefbff752"}, ] [package.dependencies] @@ -2843,6 +2968,7 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess name = "wcwidth" version = "0.2.6" description = "Measures the displayed width of unicode strings in a terminal" +category = "main" optional = false python-versions = "*" files = [ @@ -2854,6 +2980,7 @@ files = [ name = "web-py" version = "0.62" description = "web.py: makes web apps" +category = "main" optional = false python-versions = ">=3.5" files = [ @@ -2867,6 +2994,7 @@ cheroot = "*" name = "webargs" version = "5.5.3" description = "Declarative parsing and validation of HTTP request objects, with built-in support for popular web frameworks, including Flask, Django, Bottle, Tornado, Pyramid, webapp2, Falcon, and aiohttp." +category = "main" optional = false python-versions = "*" files = [ @@ -2890,6 +3018,7 @@ tests = ["Django (>=1.11.16)", "Flask (>=0.12.2)", "aiohttp (>=3.0.0)", "bottle name = "wrapt" version = "1.15.0" description = "Module for decorators, wrappers and monkey patching." +category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" files = [ @@ -2974,6 +3103,7 @@ files = [ name = "yarl" version = "1.9.2" description = "Yet another URL library" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3065,8 +3195,4 @@ indy = ["python3-indy"] [metadata] lock-version = "2.0" python-versions = "^3.9" -<<<<<<< HEAD -content-hash = "3572d33f01ad13410e0ae03d925d0ab238b66e7c888ccfb29501f72f9bec66e7" -======= -content-hash = "cdc19dba91e323e22647314394ff3e805b87378762d9ac364cfd871d78604caf" ->>>>>>> feat: add sd-jwt python library to pyproject.toml +content-hash = "72c46c5cf767dcba25d4bfc978b4d71d5f5aa1b1225813975964ae6925b84ec2" From 9d3f013b50cdae4067dd00d1fa3bf363789d630a Mon Sep 17 00:00:00 2001 From: Char Howland Date: Fri, 8 Sep 2023 15:38:30 -0600 Subject: [PATCH 62/85] chore: remove requirements.txt Signed-off-by: Char Howland --- requirements.txt | 33 --------------------------------- 1 file changed, 33 deletions(-) delete mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index cc6b92f957..0000000000 --- a/requirements.txt +++ /dev/null @@ -1,33 +0,0 @@ -aiohttp~=3.8.1 -aiohttp-apispec~=2.2.1 -aiohttp-cors~=0.7.0 -aioredis~=2.0.0 -apispec~=3.3.0 -async-timeout~=4.0.2 -base58~=2.1.0 -ConfigArgParse~=1.5.3 -deepmerge~=0.3.0 -ecdsa~=0.16.1 -jsonpath_ng==1.5.2 -Markdown~=3.1.1 -markupsafe==2.0.1 -marshmallow~=3.20.1 -msgpack~=1.0 -multiformats~=0.2.1 -nest_asyncio~=1.5.5 -packaging~=23.1 -portalocker~=2.7.0 -prompt_toolkit~=2.0.9 -pydid~=0.3.6 -pyjwt~=2.8.0 -pyld~=2.0.3 -pynacl~=1.5.0 -python-dateutil~=2.8.1 -python-json-logger~=2.0.7 -pytz~=2021.1 -pyyaml~=6.0.1 -qrcode[pil]~=6.1 -requests~=2.31.0 -rlp==1.2.0 -unflatten~=0.1 -git+https://github.com/openwallet-foundation-labs/sd-jwt-python.git@main \ No newline at end of file From 10eac810e263e9fe3c03aa2d9333d246658ed79d Mon Sep 17 00:00:00 2001 From: Char Howland Date: Wed, 13 Sep 2023 12:01:06 -0600 Subject: [PATCH 63/85] docs: sd jwt implementation Signed-off-by: Char Howland --- .../SelectiveDisclosureJWTs.md | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 docs/GettingStartedAriesDev/SelectiveDisclosureJWTs.md diff --git a/docs/GettingStartedAriesDev/SelectiveDisclosureJWTs.md b/docs/GettingStartedAriesDev/SelectiveDisclosureJWTs.md new file mode 100644 index 0000000000..305da98f30 --- /dev/null +++ b/docs/GettingStartedAriesDev/SelectiveDisclosureJWTs.md @@ -0,0 +1,89 @@ +# SD-JWT Implementation in ACA-Py + +This document describes the implementation of SD-JWTs in ACA-Py according to the [Selective Disclosure for JWTs (SD-JWT) Specification](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-selective-disclosure-jwt-05), which defines a mechanism for selective disclosure of individual elements of a JSON object used as the payload of a JSON Web Signature structure. + +This implementation adds an important privacy-preserving feature to JWTs, since the receiver of an unencrypted JWT can view all claims within. This feature allows the holder to present only a relevant subset of the claims for a given presentation. The issuer includes plaintext claims, called disclosures, outside of the JWT. Each disclosure corresponds to a hidden claim within the JWT. When a holder prepares a presentation, they include along with the JWT only the disclosures corresponding to the claims they wish to reveal. The verifier verifies that the disclosures in fact correspond to claim values within the issuer-signed JWT. The verifier cannot view the claim values not disclosed by the holder. + +In addition, this implementation includes an optional mechanism for key binding, which is the concept of binding an SD-JWT to a holder's public key and requiring that the holder prove possession of the corresponding private key when presenting the SD-JWT. + +## Issuer Instructions + +The issuer determines which claims in an SD-JWT can be selectively disclosable. In this implementation, all claims at all levels of the JSON structure are by default selectively disclosable. If the issuer wishes for certain claims to always be visible, they can indicate which claims should not be selectively disclosable, as described below. Essential verification data such as `iss`, `iat`, `exp`, and `cnf` are always visible. + +The issuer creates a list of JSON paths for the claims that will not be selectively disclosable. Here is an example payload: +``` +{ + "birthdate": "1940-01-01", + "address": { + "street_address": "123 Main St", + "locality": "Anytown", + "region": "Anystate", + "country": "US", + }, + "nationalities": ["US", "DE", "SA"], +} + +``` + +| Attribute to access | JSON path | +|--------------|-----------| +| "birthdate" | "birthdate" | +| The country attribute within the address dictionary | "address.country" | +| The second item in the nationalities list | "nationalities[1] | +| All items in the nationalities list | "nationalities[0:2]" | + +The (specification)[https://datatracker.ietf.org/doc/html/draft-ietf-oauth-selective-disclosure-jwt-05#name-nested-data-in-sd-jwts] defines options for how the issuer can handle nested structures with respect to selective disclosability. As mentioned, all claims at all levels of the JSON structure are by default selectively disclosable. + +### [Option 1: Flat SD-JWT](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-selective-disclosure-jwt-05#section-5.7.1) +The issuer can decide to treat the `address` claim in the above example payload as a block that can either be disclosed completely or not at all. + +The issuer lists out all the claims inside "address" in the `non_sd_list`, but not `address` itself: +``` +non_sd_list = [ + "address.street_address", + "address.locality", + "address.region", + "address.country", +] +``` + +### [Option 2: Structured SD-JWT](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-selective-disclosure-jwt-05#section-5.7.2) +The issuer may instead decide to make the `address` claim contents selectively disclosable individually. + +The issuer lists only "address" in the `non_sd_list`. +``` +non_sd_list = ["address"] +``` + +### [Option 3: SD-JWT with Recursive Disclosures](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-selective-disclosure-jwt-05#section-5.7.3) +The issuer may also decide to make the `address` claim contents selectively disclosable recursively, i.e., the `address` claim is made selectively disclosable as well as its sub-claims. + +The issuer lists neither `address` nor the subclaims of `address` in the `non_sd_list`, leaving all with their default selective disclosability. If all claims can be selectively disclosable, the `non_sd_list` need not be defined explicitly. + + +## Walk-Through of SD-JWT Implementation + +### Signing SD-JWTs +THe `sd_jwt_sign` method: +- Creates the list of claims that are selectively disclosable + - Uses the `non_sd_list` compared against the list of JSON paths for all claims to create the list of JSON paths for selectively disclosable claims + - Separates list splices if necessary + - Sorts the `sd_list` so that the claims deepest in the structure are handled first + - Since we will wrap the selectively disclosable claim keys, the JSON paths for nested structures do not work properly when the claim key is wrapped in an object +- Uses the JSON paths in the `sd_list` to find each selectively disclosable claim and wrap it in the `SDObj` defined by the [sd-jwt Python library](https://github.com/openwallet-foundation-labs/sd-jwt-python) and removes/replaces the original entry + - For list items, the element itself is wrapped + - For other objects, the dictionary key is wrapped +- With this modified payload, the `SDJWTIssuerACAPy.issue()` method: + - Checks if there are selectively disclosable claims at any level in the payload + - Assembles the SD-JWT payload and creates the disclosures + - Calls `SDJWTIssuerACAPy._create_signed_jws()`, which is redefined in order to use the ACA-Py `jwt_sign` method and which creates the JWT + - Combines and returns the signed JWT with its disclosures and option key binding JWT, as indicated in the [specification](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-selective-disclosure-jwt-05#name-sd-jwt-structure) + + +### Verifying SD-JWTs +`sd_jwt_verify`: +- Parses the SD-JWT presentation into its component parts: JWT, disclosures, and optional key binding + - The JWT payload is parsed from its headers and signature +- Creates a list of plaintext disclosures +- Calls `SDJWTVerifierACAPy._verify_sd_jwt`, which is redefined in order to use the ACA-Py `jwt_verify` method, and which returns the verified JWT +- If key binding is used, the key binding JWT is verified and checked against the expected audience and nonce values From 3c749527335525aea78d2754261282d3fe061336 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Wed, 13 Sep 2023 12:20:37 -0600 Subject: [PATCH 64/85] docs: typo Signed-off-by: Char Howland --- docs/GettingStartedAriesDev/SelectiveDisclosureJWTs.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/GettingStartedAriesDev/SelectiveDisclosureJWTs.md b/docs/GettingStartedAriesDev/SelectiveDisclosureJWTs.md index 305da98f30..17fcde0340 100644 --- a/docs/GettingStartedAriesDev/SelectiveDisclosureJWTs.md +++ b/docs/GettingStartedAriesDev/SelectiveDisclosureJWTs.md @@ -64,7 +64,7 @@ The issuer lists neither `address` nor the subclaims of `address` in the `non_sd ## Walk-Through of SD-JWT Implementation ### Signing SD-JWTs -THe `sd_jwt_sign` method: +The `sd_jwt_sign` method: - Creates the list of claims that are selectively disclosable - Uses the `non_sd_list` compared against the list of JSON paths for all claims to create the list of JSON paths for selectively disclosable claims - Separates list splices if necessary @@ -81,7 +81,7 @@ THe `sd_jwt_sign` method: ### Verifying SD-JWTs -`sd_jwt_verify`: +The `sd_jwt_verify` method: - Parses the SD-JWT presentation into its component parts: JWT, disclosures, and optional key binding - The JWT payload is parsed from its headers and signature - Creates a list of plaintext disclosures From 522a6c1dc919dd5a18a7ec332100c36974490d63 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Thu, 14 Sep 2023 11:49:18 -0600 Subject: [PATCH 65/85] fix: sd-jwt regex to allow key binding JWT Signed-off-by: Char Howland --- aries_cloudagent/messaging/valid.py | 2 +- aries_cloudagent/wallet/routes.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/aries_cloudagent/messaging/valid.py b/aries_cloudagent/messaging/valid.py index ab1010f3b6..52fb123f35 100644 --- a/aries_cloudagent/messaging/valid.py +++ b/aries_cloudagent/messaging/valid.py @@ -250,7 +250,7 @@ class SDJSONWebToken(Regexp): "~WyJPMTFySVRjRTdHcXExYW9oRkd0aDh3IiwgIlNBIl0" "~WyJkVmEzX1JlTGNsWTU0R1FHZm5oWlRnIiwgInVwZGF0ZWRfYXQiLCAxNTcwMDAwMDAwXQ" ) - PATTERN = r"^[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]*\.[a-zA-Z0-9_-]+(?:~[a-zA-Z0-9_-]+)*~?$" + PATTERN = r"^[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]*\.[a-zA-Z0-9_-]+(?:~[a-zA-Z0-9._-]+)*~?$" def __init__(self): """Initialize the instance.""" diff --git a/aries_cloudagent/wallet/routes.py b/aries_cloudagent/wallet/routes.py index 22481b2d6a..e440ba9469 100644 --- a/aries_cloudagent/wallet/routes.py +++ b/aries_cloudagent/wallet/routes.py @@ -1052,7 +1052,9 @@ async def wallet_jwt_verify(request: web.BaseRequest): @docs( - tags=["wallet"], summary="Verify a EdDSA sd-jws using did keys with a given SD-JWS" + tags=["wallet"], + summary="Verify a EdDSA sd-jws using did keys with a given SD-JWS with " + "optional key binding", ) @request_schema(SDJWSVerifySchema()) @response_schema(SDJWSVerifyResponseSchema(), 200, description="") From 636ed4445a1924c7d54616b3e1afac152e6e3c0f Mon Sep 17 00:00:00 2001 From: Char Howland Date: Thu, 14 Sep 2023 11:50:33 -0600 Subject: [PATCH 66/85] docs: example inputs to admin api endpoints Signed-off-by: Char Howland --- .../SelectiveDisclosureJWTs.md | 109 +++++++++++++++++- 1 file changed, 107 insertions(+), 2 deletions(-) diff --git a/docs/GettingStartedAriesDev/SelectiveDisclosureJWTs.md b/docs/GettingStartedAriesDev/SelectiveDisclosureJWTs.md index 17fcde0340..2472c709a3 100644 --- a/docs/GettingStartedAriesDev/SelectiveDisclosureJWTs.md +++ b/docs/GettingStartedAriesDev/SelectiveDisclosureJWTs.md @@ -64,7 +64,47 @@ The issuer lists neither `address` nor the subclaims of `address` in the `non_sd ## Walk-Through of SD-JWT Implementation ### Signing SD-JWTs -The `sd_jwt_sign` method: + +#### Example input to `/wallet/sd-jwt/sign` endpoint: + +``` +{ + "did": "WpVJtxKVwGQdRpQP8iwJZy", + "headers": {}, + "payload": { + "sub": "user_42", + "given_name": "John", + "family_name": "Doe", + "email": "johndoe@example.com", + "phone_number": "+1-202-555-0101", + "phone_number_verified": true, + "address": { + "street_address": "123 Main St", + "locality": "Anytown", + "region": "Anystate", + "country": "US" + }, + "birthdate": "1940-01-01", + "updated_at": 1570000000, + "nationalities": ["US", "DE", "SA"], + "iss": "https://example.com/issuer", + "iat": 1683000000, + "exp": 1883000000 + }, + "non_sd_list": [ + "given_name", + "family_name", + "nationalities" + ] +} + +``` +#### Output: +``` +"eyJ0eXAiOiAiSldUIiwgImFsZyI6ICJFZERTQSIsICJraWQiOiAiZGlkOnNvdjpXcFZKdHhLVndHUWRScFFQOGl3Slp5I2tleS0xIn0.eyJfc2QiOiBbIkR0a21ha3NkZGtHRjFKeDBDY0kxdmxRTmZMcGFnQWZ1N3p4VnBGRWJXeXciLCAiSlJLb1E0QXVHaU1INWJIanNmNVV4YmJFeDh2YzFHcUtvX0l3TXE3Nl9xbyIsICJNTTh0TlVLNUstR1lWd0swX01kN0k4MzExTTgwVi13Z0hRYWZvRkoxS09JIiwgIlBaM1VDQmdadVRMMDJkV0pxSVY4elUtSWhnalJNX1NTS3dQdTk3MURmLTQiLCAiX294WGNuSW5Yai1SV3BMVHNISU5YaHFrRVAwODkwUFJjNDBISWE1NElJMCIsICJhdnRLVW5Sdnc1clV0TnZfUnAwUll1dUdkR0RzcnJPYWJfVjR1Y05RRWRvIiwgInByRXZJbzBseTVtNTVsRUpTQUdTVzMxWGdVTElOalo5ZkxiRG81U1pCX0UiXSwgImdpdmVuX25hbWUiOiAiSm9obiIsICJmYW1pbHlfbmFtZSI6ICJEb2UiLCAibmF0aW9uYWxpdGllcyI6IFt7Ii4uLiI6ICJPdU1wcEhpYzEySjYzWTBIY2Ffd1BVeDJCTGdUQVdZQjJpdXpMY3lvcU5JIn0sIHsiLi4uIjogIlIxczlaU3NYeVV0T2QyODdEYy1DTVYyMEdvREF3WUVHV3c4ZkVKd1BNMjAifSwgeyIuLi4iOiAid0lJbjdhQlNDVkFZcUF1Rks3Nmpra3FjVGFvb3YzcUhKbzU5WjdKWHpnUSJ9XSwgImlzcyI6ICJodHRwczovL2V4YW1wbGUuY29tL2lzc3VlciIsICJpYXQiOiAxNjgzMDAwMDAwLCAiZXhwIjogMTg4MzAwMDAwMCwgIl9zZF9hbGciOiAic2hhLTI1NiJ9.cIsuGTIPfpRs_Z49nZcn7L6NUgxQumMGQpu8K6rBtv-YRiFyySUgthQI8KZe1xKyn5Wc8zJnRcWbFki2Vzw6Cw~WyJmWURNM1FQcnZicnZ6YlN4elJsUHFnIiwgIlNBIl0~WyI0UGc2SmZ0UnRXdGFPcDNZX2tscmZRIiwgIkRFIl0~WyJBcDh1VHgxbVhlYUgxeTJRRlVjbWV3IiwgIlVTIl0~WyJ4dkRYMDBmalpmZXJpTmlQb2Q1MXFRIiwgInVwZGF0ZWRfYXQiLCAxNTcwMDAwMDAwXQ~WyJYOTlzM19MaXhCY29yX2hudFJFWmNnIiwgInN1YiIsICJ1c2VyXzQyIl0~WyIxODVTak1hM1k3QlFiWUpabVE3U0NRIiwgInBob25lX251bWJlcl92ZXJpZmllZCIsIHRydWVd~WyJRN1FGaUpvZkhLSWZGV0kxZ0Vaal93IiwgInBob25lX251bWJlciIsICIrMS0yMDItNTU1LTAxMDEiXQ~WyJOeWtVcmJYN1BjVE1ubVRkUWVxZXl3IiwgImVtYWlsIiwgImpvaG5kb2VAZXhhbXBsZS5jb20iXQ~WyJlemJwQ2lnVlhrY205RlluVjNQMGJ3IiwgImJpcnRoZGF0ZSIsICIxOTQwLTAxLTAxIl0~WyJvd3ROX3I5Z040MzZKVnJFRWhQU05BIiwgInN0cmVldF9hZGRyZXNzIiwgIjEyMyBNYWluIFN0Il0~WyJLQXktZ0VaWmRiUnNHV1dNVXg5amZnIiwgInJlZ2lvbiIsICJBbnlzdGF0ZSJd~WyJPNnl0anM2SU9HMHpDQktwa0tzU1pBIiwgImxvY2FsaXR5IiwgIkFueXRvd24iXQ~WyI0Nzg5aG5GSjhFNTRsLW91RjRaN1V3IiwgImNvdW50cnkiLCAiVVMiXQ~WyIyaDR3N0FuaDFOOC15ZlpGc2FGVHRBIiwgImFkZHJlc3MiLCB7Il9zZCI6IFsiTXhKRDV5Vm9QQzFIQnhPRmVRa21TQ1E0dVJrYmNrellza1Z5RzVwMXZ5SSIsICJVYkxmVWlpdDJTOFhlX2pYbS15RHBHZXN0ZDNZOGJZczVGaVJpbVBtMHdvIiwgImhsQzJEYVBwT2t0eHZyeUFlN3U2YnBuM09IZ193Qk5heExiS3lPRDVMdkEiLCAia2NkLVJNaC1PaGFZS1FPZ2JaajhmNUppOXNLb2hyYnlhYzNSdXRqcHNNYyJdfV0~" +``` + +The `sd_jwt_sign()` method: - Creates the list of claims that are selectively disclosable - Uses the `non_sd_list` compared against the list of JSON paths for all claims to create the list of JSON paths for selectively disclosable claims - Separates list splices if necessary @@ -80,8 +120,73 @@ The `sd_jwt_sign` method: - Combines and returns the signed JWT with its disclosures and option key binding JWT, as indicated in the [specification](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-selective-disclosure-jwt-05#name-sd-jwt-structure) + ### Verifying SD-JWTs -The `sd_jwt_verify` method: + +#### Example input to `/wallet/sd-jwt/verify` endpoint: + +Using the output from the `/wallet/sd-jwt/sign` example above, we have decided to only reveal two of the selectively disclosable claims (`user` and `updated_at`) and achieved this by only including the disclosures for those claims. We have also included a key binding JWT following the disclosures. +``` +{ + "sd_jwt": "eyJ0eXAiOiAiSldUIiwgImFsZyI6ICJFZERTQSIsICJraWQiOiAiZGlkOnNvdjpXcFZKdHhLVndHUWRScFFQOGl3Slp5I2tleS0xIn0.eyJfc2QiOiBbIkR0a21ha3NkZGtHRjFKeDBDY0kxdmxRTmZMcGFnQWZ1N3p4VnBGRWJXeXciLCAiSlJLb1E0QXVHaU1INWJIanNmNVV4YmJFeDh2YzFHcUtvX0l3TXE3Nl9xbyIsICJNTTh0TlVLNUstR1lWd0swX01kN0k4MzExTTgwVi13Z0hRYWZvRkoxS09JIiwgIlBaM1VDQmdadVRMMDJkV0pxSVY4elUtSWhnalJNX1NTS3dQdTk3MURmLTQiLCAiX294WGNuSW5Yai1SV3BMVHNISU5YaHFrRVAwODkwUFJjNDBISWE1NElJMCIsICJhdnRLVW5Sdnc1clV0TnZfUnAwUll1dUdkR0RzcnJPYWJfVjR1Y05RRWRvIiwgInByRXZJbzBseTVtNTVsRUpTQUdTVzMxWGdVTElOalo5ZkxiRG81U1pCX0UiXSwgImdpdmVuX25hbWUiOiAiSm9obiIsICJmYW1pbHlfbmFtZSI6ICJEb2UiLCAibmF0aW9uYWxpdGllcyI6IFt7Ii4uLiI6ICJPdU1wcEhpYzEySjYzWTBIY2Ffd1BVeDJCTGdUQVdZQjJpdXpMY3lvcU5JIn0sIHsiLi4uIjogIlIxczlaU3NYeVV0T2QyODdEYy1DTVYyMEdvREF3WUVHV3c4ZkVKd1BNMjAifSwgeyIuLi4iOiAid0lJbjdhQlNDVkFZcUF1Rks3Nmpra3FjVGFvb3YzcUhKbzU5WjdKWHpnUSJ9XSwgImlzcyI6ICJodHRwczovL2V4YW1wbGUuY29tL2lzc3VlciIsICJpYXQiOiAxNjgzMDAwMDAwLCAiZXhwIjogMTg4MzAwMDAwMCwgIl9zZF9hbGciOiAic2hhLTI1NiJ9.cIsuGTIPfpRs_Z49nZcn7L6NUgxQumMGQpu8K6rBtv-YRiFyySUgthQI8KZe1xKyn5Wc8zJnRcWbFki2Vzw6Cw~WyJ4dkRYMDBmalpmZXJpTmlQb2Q1MXFRIiwgInVwZGF0ZWRfYXQiLCAxNTcwMDAwMDAwXQ~WyJYOTlzM19MaXhCY29yX2hudFJFWmNnIiwgInN1YiIsICJ1c2VyXzQyIl0~eyJhbGciOiAiRWREU0EiLCAidHlwIjogImtiK2p3dCIsICJraWQiOiAiZGlkOnNvdjpXcFZKdHhLVndHUWRScFFQOGl3Slp5I2tleS0xIn0.eyJub25jZSI6ICIxMjM0NTY3ODkwIiwgImF1ZCI6ICJodHRwczovL2V4YW1wbGUuY29tL3ZlcmlmaWVyIiwgImlhdCI6IDE2ODgxNjA0ODN9.i55VeR7bNt7T8HWJcfj6jSLH3Q7vFk8N0t7Tb5FZHKmiHyLrg0IPAuK5uKr3_4SkjuGt1_iNl8Wr3atWBtXMDA" +} +``` + +#### Output: +Note that attributes in the `non_sd_list` (`given_name`, `family_name`, and `nationalities`), as well as essential verification data (`iss`, `iat`, `exp`) are visible directly within the payload. The disclosures include only the values for the `user` and `updated_at` claims, since those are the only selectively disclosable claims that the holder presented. The corresponding hashes for those disclosures appear in the `payload["_sd"]` list. +``` +{ + "headers": { + "typ": "JWT", + "alg": "EdDSA", + "kid": "did:sov:WpVJtxKVwGQdRpQP8iwJZy#key-1" + }, + "payload": { + "_sd": [ + "DtkmaksddkGF1Jx0CcI1vlQNfLpagAfu7zxVpFEbWyw", + "JRKoQ4AuGiMH5bHjsf5UxbbEx8vc1GqKo_IwMq76_qo", + "MM8tNUK5K-GYVwK0_Md7I8311M80V-wgHQafoFJ1KOI", + "PZ3UCBgZuTL02dWJqIV8zU-IhgjRM_SSKwPu971Df-4", + "_oxXcnInXj-RWpLTsHINXhqkEP0890PRc40HIa54II0", + "avtKUnRvw5rUtNv_Rp0RYuuGdGDsrrOab_V4ucNQEdo", + "prEvIo0ly5m55lEJSAGSW31XgULINjZ9fLbDo5SZB_E" + ], + "given_name": "John", + "family_name": "Doe", + "nationalities": [ + { + "...": "OuMppHic12J63Y0Hca_wPUx2BLgTAWYB2iuzLcyoqNI" + }, + { + "...": "R1s9ZSsXyUtOd287Dc-CMV20GoDAwYEGWw8fEJwPM20" + }, + { + "...": "wIIn7aBSCVAYqAuFK76jkkqcTaoov3qHJo59Z7JXzgQ" + } + ], + "iss": "https://example.com/issuer", + "iat": 1683000000, + "exp": 1883000000, + "_sd_alg": "sha-256" + }, + "valid": true, + "kid": "did:sov:WpVJtxKVwGQdRpQP8iwJZy#key-1", + "disclosures": [ + [ + "xvDX00fjZferiNiPod51qQ", + "updated_at", + 1570000000 + ], + [ + "X99s3_LixBcor_hntREZcg", + "sub", + "user_42" + ] + ] +} +``` + +The `sd_jwt_verify()` method: - Parses the SD-JWT presentation into its component parts: JWT, disclosures, and optional key binding - The JWT payload is parsed from its headers and signature - Creates a list of plaintext disclosures From 5d0335fd6a28e7e0a041074d3d58abe845d93936 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Mon, 18 Sep 2023 21:52:28 -0600 Subject: [PATCH 67/85] chore: use alias for jsonpath parse Signed-off-by: Char Howland --- aries_cloudagent/wallet/sd_jwt.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aries_cloudagent/wallet/sd_jwt.py b/aries_cloudagent/wallet/sd_jwt.py index 142ff2ca07..59f3291995 100644 --- a/aries_cloudagent/wallet/sd_jwt.py +++ b/aries_cloudagent/wallet/sd_jwt.py @@ -3,7 +3,7 @@ import re from typing import Any, List, Mapping, Optional, Union from marshmallow import fields -from jsonpath_ng.ext import parse +from jsonpath_ng.ext import parse as jsonpath_parse from sd_jwt.common import SDObj from sd_jwt.issuer import SDJWTIssuer from sd_jwt.verifier import SDJWTVerifier @@ -152,7 +152,7 @@ async def sd_jwt_sign( """ sd_list = create_sd_list(payload, non_sd_list) for sd in sd_list: - jsonpath_expression = parse(f"$.{sd}") + jsonpath_expression = jsonpath_parse(f"$.{sd}") matches = jsonpath_expression.find(payload) if len(matches) < 1: raise SDJWTError(f"Claim for {sd} not found in payload.") From 97f7d69bf37bb015b103cfb0f956958c7ad42074 Mon Sep 17 00:00:00 2001 From: Char Howland Date: Mon, 18 Sep 2023 22:13:47 -0600 Subject: [PATCH 68/85] docs: fix markdown link Signed-off-by: Char Howland --- docs/GettingStartedAriesDev/SelectiveDisclosureJWTs.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/GettingStartedAriesDev/SelectiveDisclosureJWTs.md b/docs/GettingStartedAriesDev/SelectiveDisclosureJWTs.md index 2472c709a3..ed21f04768 100644 --- a/docs/GettingStartedAriesDev/SelectiveDisclosureJWTs.md +++ b/docs/GettingStartedAriesDev/SelectiveDisclosureJWTs.md @@ -32,7 +32,7 @@ The issuer creates a list of JSON paths for the claims that will not be selectiv | The second item in the nationalities list | "nationalities[1] | | All items in the nationalities list | "nationalities[0:2]" | -The (specification)[https://datatracker.ietf.org/doc/html/draft-ietf-oauth-selective-disclosure-jwt-05#name-nested-data-in-sd-jwts] defines options for how the issuer can handle nested structures with respect to selective disclosability. As mentioned, all claims at all levels of the JSON structure are by default selectively disclosable. +The [specification](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-selective-disclosure-jwt-05#name-nested-data-in-sd-jwts) defines options for how the issuer can handle nested structures with respect to selective disclosability. As mentioned, all claims at all levels of the JSON structure are by default selectively disclosable. ### [Option 1: Flat SD-JWT](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-selective-disclosure-jwt-05#section-5.7.1) The issuer can decide to treat the `address` claim in the above example payload as a block that can either be disclosed completely or not at all. From cb4ca9ba4836dfd37dcd2d621f4bc1ccc5a05e16 Mon Sep 17 00:00:00 2001 From: Lucas ONeil Date: Wed, 20 Sep 2023 21:09:59 -0700 Subject: [PATCH 69/85] Use correct rust log level Signed-off-by: Lucas ONeil --- docker/Dockerfile | 2 +- docker/Dockerfile.indy | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index c294d0b12c..df4ca3e25d 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -22,7 +22,7 @@ ENV HOME="/home/$user" \ PIP_NO_CACHE_DIR=off \ PYTHONUNBUFFERED=1 \ PYTHONIOENCODING=UTF-8 \ - RUST_LOG=warning \ + RUST_LOG=warn \ SHELL=/bin/bash \ SUMMARY="aries-cloudagent image" \ DESCRIPTION="aries-cloudagent provides a base image for running Hyperledger Aries agents in Docker. \ diff --git a/docker/Dockerfile.indy b/docker/Dockerfile.indy index ac3ecd20a7..66b922b2ee 100644 --- a/docker/Dockerfile.indy +++ b/docker/Dockerfile.indy @@ -93,7 +93,7 @@ ENV HOME="/home/$user" \ PIP_NO_CACHE_DIR=off \ PYTHONUNBUFFERED=1 \ PYTHONIOENCODING=UTF-8 \ - RUST_LOG=warning \ + RUST_LOG=warn \ SHELL=/bin/bash \ SUMMARY="indy-python base image" \ DESCRIPTION="aries-cloudagent provides a base image for running Hyperledger Aries agents in Docker. \ @@ -227,7 +227,7 @@ ENV HOME="/home/$user" \ PIP_NO_CACHE_DIR=off \ PYTHONUNBUFFERED=1 \ PYTHONIOENCODING=UTF-8 \ - RUST_LOG=warning \ + RUST_LOG=warn \ SHELL=/bin/bash \ SUMMARY="aries-cloudagent image" \ DESCRIPTION="aries-cloudagent provides a base image for running Hyperledger Aries agents in Docker. \ From 83c412151c1f5fcd5245a49312ed40c8321debc1 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Fri, 22 Sep 2023 14:15:00 -0700 Subject: [PATCH 70/85] remove unused dependencies Signed-off-by: Andrew Whitehead --- NOTICES | 19 -- poetry.lock | 582 +++++++++++-------------------------------------- pyproject.toml | 5 - 3 files changed, 130 insertions(+), 476 deletions(-) diff --git a/NOTICES b/NOTICES index a24b3a4eee..bf9636c516 100644 --- a/NOTICES +++ b/NOTICES @@ -355,25 +355,6 @@ THE SOFTWARE. ----------------------------------------------------------------- ----------------------------------------------------------------- -=> msgpack - -Copyright (C) 2008-2011 INADA Naoki - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - ------------------------------------------------------------------ ------------------------------------------------------------------ - => requests Copyright (C) 2019 Kenneith Reitz diff --git a/poetry.lock b/poetry.lock index f2cda674e2..ebd2184a66 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,10 +1,9 @@ -# This file is automatically @generated by Poetry and should not be changed by hand. +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. [[package]] name = "aiohttp" version = "3.8.5" description = "Async http client/server framework (asyncio)" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -113,7 +112,6 @@ speedups = ["Brotli", "aiodns", "cchardet"] name = "aiohttp-apispec" version = "2.2.3" description = "Build and document REST APIs with aiohttp and apispec" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -130,7 +128,6 @@ webargs = "<6.0" name = "aiohttp-cors" version = "0.7.0" description = "CORS support for aiohttp" -category = "main" optional = false python-versions = "*" files = [ @@ -141,30 +138,10 @@ files = [ [package.dependencies] aiohttp = ">=1.1" -[[package]] -name = "aioredis" -version = "2.0.1" -description = "asyncio (PEP 3156) Redis support" -category = "main" -optional = false -python-versions = ">=3.6" -files = [ - {file = "aioredis-2.0.1-py3-none-any.whl", hash = "sha256:9ac0d0b3b485d293b8ca1987e6de8658d7dafcca1cddfcd1d506cae8cdebfdd6"}, - {file = "aioredis-2.0.1.tar.gz", hash = "sha256:eaa51aaf993f2d71f54b70527c440437ba65340588afeb786cd87c55c89cd98e"}, -] - -[package.dependencies] -async-timeout = "*" -typing-extensions = "*" - -[package.extras] -hiredis = ["hiredis (>=1.0)"] - [[package]] name = "aiosignal" version = "1.3.1" description = "aiosignal: a list of registered asynchronous callbacks" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -179,7 +156,6 @@ frozenlist = ">=1.1.0" name = "alabaster" version = "0.7.13" description = "A configurable sidebar-enabled Sphinx theme" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -191,7 +167,6 @@ files = [ name = "apispec" version = "3.3.2" description = "A pluggable API specification generator. Currently supports the OpenAPI Specification (f.k.a. the Swagger specification)." -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -211,7 +186,6 @@ yaml = ["PyYAML (>=3.10)"] name = "aries-askar" version = "0.2.9" description = "" -category = "main" optional = true python-versions = ">=3.6.3" files = [ @@ -227,7 +201,6 @@ cached-property = ">=1.5,<2.0" name = "async-timeout" version = "4.0.3" description = "Timeout context manager for asyncio programs" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -235,52 +208,10 @@ files = [ {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, ] -[[package]] -name = "asyncpg" -version = "0.25.0" -description = "An asyncio PostgreSQL driver" -category = "main" -optional = false -python-versions = ">=3.6.0" -files = [ - {file = "asyncpg-0.25.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bf5e3408a14a17d480f36ebaf0401a12ff6ae5457fdf45e4e2775c51cc9517d3"}, - {file = "asyncpg-0.25.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2bc197fc4aca2fd24f60241057998124012469d2e414aed3f992579db0c88e3a"}, - {file = "asyncpg-0.25.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1a70783f6ffa34cc7dd2de20a873181414a34fd35a4a208a1f1a7f9f695e4ec4"}, - {file = "asyncpg-0.25.0-cp310-cp310-win32.whl", hash = "sha256:43cde84e996a3afe75f325a68300093425c2f47d340c0fc8912765cf24a1c095"}, - {file = "asyncpg-0.25.0-cp310-cp310-win_amd64.whl", hash = "sha256:56d88d7ef4341412cd9c68efba323a4519c916979ba91b95d4c08799d2ff0c09"}, - {file = "asyncpg-0.25.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a84d30e6f850bac0876990bcd207362778e2208df0bee8be8da9f1558255e634"}, - {file = "asyncpg-0.25.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:beaecc52ad39614f6ca2e48c3ca15d56e24a2c15cbfdcb764a4320cc45f02fd5"}, - {file = "asyncpg-0.25.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:6f8f5fc975246eda83da8031a14004b9197f510c41511018e7b1bedde6968e92"}, - {file = "asyncpg-0.25.0-cp36-cp36m-win32.whl", hash = "sha256:ddb4c3263a8d63dcde3d2c4ac1c25206bfeb31fa83bd70fd539e10f87739dee4"}, - {file = "asyncpg-0.25.0-cp36-cp36m-win_amd64.whl", hash = "sha256:bf6dc9b55b9113f39eaa2057337ce3f9ef7de99a053b8a16360395ce588925cd"}, - {file = "asyncpg-0.25.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:acb311722352152936e58a8ee3c5b8e791b24e84cd7d777c414ff05b3530ca68"}, - {file = "asyncpg-0.25.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0a61fb196ce4dae2f2fa26eb20a778db21bbee484d2e798cb3cc988de13bdd1b"}, - {file = "asyncpg-0.25.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2633331cbc8429030b4f20f712f8d0fbba57fa8555ee9b2f45f981b81328b256"}, - {file = "asyncpg-0.25.0-cp37-cp37m-win32.whl", hash = "sha256:863d36eba4a7caa853fd7d83fad5fd5306f050cc2fe6e54fbe10cdb30420e5e9"}, - {file = "asyncpg-0.25.0-cp37-cp37m-win_amd64.whl", hash = "sha256:fe471ccd915b739ca65e2e4dbd92a11b44a5b37f2e38f70827a1c147dafe0fa8"}, - {file = "asyncpg-0.25.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:72a1e12ea0cf7c1e02794b697e3ca967b2360eaa2ce5d4bfdd8604ec2d6b774b"}, - {file = "asyncpg-0.25.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4327f691b1bdb222df27841938b3e04c14068166b3a97491bec2cb982f49f03e"}, - {file = "asyncpg-0.25.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:739bbd7f89a2b2f6bc44cb8bf967dab12c5bc714fcbe96e68d512be45ecdf962"}, - {file = "asyncpg-0.25.0-cp38-cp38-win32.whl", hash = "sha256:18d49e2d93a7139a2fdbd113e320cc47075049997268a61bfbe0dde680c55471"}, - {file = "asyncpg-0.25.0-cp38-cp38-win_amd64.whl", hash = "sha256:191fe6341385b7fdea7dbdcf47fd6db3fd198827dcc1f2b228476d13c05a03c6"}, - {file = "asyncpg-0.25.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:52fab7f1b2c29e187dd8781fce896249500cf055b63471ad66332e537e9b5f7e"}, - {file = "asyncpg-0.25.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a738f1b2876f30d710d3dc1e7858160a0afe1603ba16bf5f391f5316eb0ed855"}, - {file = "asyncpg-0.25.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5e4105f57ad1e8fbc8b1e535d8fcefa6ce6c71081228f08680c6dea24384ff0e"}, - {file = "asyncpg-0.25.0-cp39-cp39-win32.whl", hash = "sha256:f55918ded7b85723a5eaeb34e86e7b9280d4474be67df853ab5a7fa0cc7c6bf2"}, - {file = "asyncpg-0.25.0-cp39-cp39-win_amd64.whl", hash = "sha256:649e2966d98cc48d0646d9a4e29abecd8b59d38d55c256d5c857f6b27b7407ac"}, - {file = "asyncpg-0.25.0.tar.gz", hash = "sha256:63f8e6a69733b285497c2855464a34de657f2cccd25aeaeeb5071872e9382540"}, -] - -[package.extras] -dev = ["Cython (>=0.29.24,<0.30.0)", "Sphinx (>=4.1.2,<4.2.0)", "flake8 (>=3.9.2,<3.10.0)", "pycodestyle (>=2.7.0,<2.8.0)", "pytest (>=6.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)", "uvloop (>=0.15.3)"] -docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"] -test = ["flake8 (>=3.9.2,<3.10.0)", "pycodestyle (>=2.7.0,<2.8.0)", "uvloop (>=0.15.3)"] - [[package]] name = "asynctest" version = "0.13.0" description = "Enhance the standard unittest package with features for testing asyncio libraries" -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -292,7 +223,6 @@ files = [ name = "attrs" version = "23.1.0" description = "Classes Without Boilerplate" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -311,7 +241,6 @@ tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pyte name = "babel" version = "2.12.1" description = "Internationalization utilities" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -323,7 +252,6 @@ files = [ name = "base58" version = "2.1.1" description = "Base58 and Base58Check implementation." -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -338,7 +266,6 @@ tests = ["PyHamcrest (>=2.0.2)", "mypy", "pytest (>=4.6)", "pytest-benchmark", " name = "bases" version = "0.2.1" description = "Python library for general Base-N encodings." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -357,7 +284,6 @@ dev = ["base58", "mypy", "pylint", "pytest", "pytest-cov"] name = "black" version = "23.7.0" description = "The uncompromising code formatter." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -404,7 +330,6 @@ uvloop = ["uvloop (>=0.15.2)"] name = "cached-property" version = "1.5.2" description = "A decorator for caching properties in classes." -category = "main" optional = true python-versions = "*" files = [ @@ -416,7 +341,6 @@ files = [ name = "cachetools" version = "5.3.1" description = "Extensible memoizing collections and decorators" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -428,7 +352,6 @@ files = [ name = "certifi" version = "2023.7.22" description = "Python package for providing Mozilla's CA Bundle." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -440,7 +363,6 @@ files = [ name = "cffi" version = "1.15.1" description = "Foreign Function Interface for Python calling C code." -category = "main" optional = false python-versions = "*" files = [ @@ -517,7 +439,6 @@ pycparser = "*" name = "cfgv" version = "3.4.0" description = "Validate configuration and produce human readable error messages." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -529,7 +450,6 @@ files = [ name = "charset-normalizer" version = "3.2.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -610,30 +530,10 @@ files = [ {file = "charset_normalizer-3.2.0-py3-none-any.whl", hash = "sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6"}, ] -[[package]] -name = "cheroot" -version = "10.0.0" -description = "Highly-optimized, pure-python HTTP server" -category = "main" -optional = false -python-versions = ">=3.6" -files = [ - {file = "cheroot-10.0.0-py3-none-any.whl", hash = "sha256:8f65dd38ad3d56419cfe2d1b5e4b4e3282b1d58758ca2a336231641a80cf0717"}, - {file = "cheroot-10.0.0.tar.gz", hash = "sha256:59c4a1877fef9969b3c3c080caaaf377e2780919437853fc0d32a9df40b311f0"}, -] - -[package.dependencies] -"jaraco.functools" = "*" -more-itertools = ">=2.6" - -[package.extras] -docs = ["furo", "jaraco.packaging (>=3.2)", "python-dateutil", "sphinx (>=1.8.2)", "sphinx-tabs (>=1.1.0)", "sphinxcontrib-apidoc (>=0.3.0)"] - [[package]] name = "click" version = "8.1.7" description = "Composable command line interface toolkit" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -648,7 +548,6 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""} name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ @@ -660,7 +559,6 @@ files = [ name = "configargparse" version = "1.5.5" description = "A drop-in replacement for argparse that allows options to also be set via config files and/or environment variables." -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -676,7 +574,6 @@ yaml = ["PyYAML"] name = "coverage" version = "7.3.1" description = "Code coverage measurement for Python" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -739,35 +636,34 @@ toml = ["tomli"] [[package]] name = "cryptography" -version = "41.0.3" +version = "41.0.4" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." -category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-41.0.3-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:652627a055cb52a84f8c448185922241dd5217443ca194d5739b44612c5e6507"}, - {file = "cryptography-41.0.3-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:8f09daa483aedea50d249ef98ed500569841d6498aa9c9f4b0531b9964658922"}, - {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fd871184321100fb400d759ad0cddddf284c4b696568204d281c902fc7b0d81"}, - {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84537453d57f55a50a5b6835622ee405816999a7113267739a1b4581f83535bd"}, - {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:3fb248989b6363906827284cd20cca63bb1a757e0a2864d4c1682a985e3dca47"}, - {file = "cryptography-41.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:42cb413e01a5d36da9929baa9d70ca90d90b969269e5a12d39c1e0d475010116"}, - {file = "cryptography-41.0.3-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:aeb57c421b34af8f9fe830e1955bf493a86a7996cc1338fe41b30047d16e962c"}, - {file = "cryptography-41.0.3-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:6af1c6387c531cd364b72c28daa29232162010d952ceb7e5ca8e2827526aceae"}, - {file = "cryptography-41.0.3-cp37-abi3-win32.whl", hash = "sha256:0d09fb5356f975974dbcb595ad2d178305e5050656affb7890a1583f5e02a306"}, - {file = "cryptography-41.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:a983e441a00a9d57a4d7c91b3116a37ae602907a7618b882c8013b5762e80574"}, - {file = "cryptography-41.0.3-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5259cb659aa43005eb55a0e4ff2c825ca111a0da1814202c64d28a985d33b087"}, - {file = "cryptography-41.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:67e120e9a577c64fe1f611e53b30b3e69744e5910ff3b6e97e935aeb96005858"}, - {file = "cryptography-41.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:7efe8041897fe7a50863e51b77789b657a133c75c3b094e51b5e4b5cec7bf906"}, - {file = "cryptography-41.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ce785cf81a7bdade534297ef9e490ddff800d956625020ab2ec2780a556c313e"}, - {file = "cryptography-41.0.3-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:57a51b89f954f216a81c9d057bf1a24e2f36e764a1ca9a501a6964eb4a6800dd"}, - {file = "cryptography-41.0.3-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:4c2f0d35703d61002a2bbdcf15548ebb701cfdd83cdc12471d2bae80878a4207"}, - {file = "cryptography-41.0.3-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:23c2d778cf829f7d0ae180600b17e9fceea3c2ef8b31a99e3c694cbbf3a24b84"}, - {file = "cryptography-41.0.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:95dd7f261bb76948b52a5330ba5202b91a26fbac13ad0e9fc8a3ac04752058c7"}, - {file = "cryptography-41.0.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:41d7aa7cdfded09b3d73a47f429c298e80796c8e825ddfadc84c8a7f12df212d"}, - {file = "cryptography-41.0.3-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d0d651aa754ef58d75cec6edfbd21259d93810b73f6ec246436a21b7841908de"}, - {file = "cryptography-41.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:ab8de0d091acbf778f74286f4989cf3d1528336af1b59f3e5d2ebca8b5fe49e1"}, - {file = "cryptography-41.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a74fbcdb2a0d46fe00504f571a2a540532f4c188e6ccf26f1f178480117b33c4"}, - {file = "cryptography-41.0.3.tar.gz", hash = "sha256:6d192741113ef5e30d89dcb5b956ef4e1578f304708701b8b73d38e3e1461f34"}, + {file = "cryptography-41.0.4-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:80907d3faa55dc5434a16579952ac6da800935cd98d14dbd62f6f042c7f5e839"}, + {file = "cryptography-41.0.4-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:35c00f637cd0b9d5b6c6bd11b6c3359194a8eba9c46d4e875a3660e3b400005f"}, + {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cecfefa17042941f94ab54f769c8ce0fe14beff2694e9ac684176a2535bf9714"}, + {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e40211b4923ba5a6dc9769eab704bdb3fbb58d56c5b336d30996c24fcf12aadb"}, + {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:23a25c09dfd0d9f28da2352503b23e086f8e78096b9fd585d1d14eca01613e13"}, + {file = "cryptography-41.0.4-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2ed09183922d66c4ec5fdaa59b4d14e105c084dd0febd27452de8f6f74704143"}, + {file = "cryptography-41.0.4-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:5a0f09cefded00e648a127048119f77bc2b2ec61e736660b5789e638f43cc397"}, + {file = "cryptography-41.0.4-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:9eeb77214afae972a00dee47382d2591abe77bdae166bda672fb1e24702a3860"}, + {file = "cryptography-41.0.4-cp37-abi3-win32.whl", hash = "sha256:3b224890962a2d7b57cf5eeb16ccaafba6083f7b811829f00476309bce2fe0fd"}, + {file = "cryptography-41.0.4-cp37-abi3-win_amd64.whl", hash = "sha256:c880eba5175f4307129784eca96f4e70b88e57aa3f680aeba3bab0e980b0f37d"}, + {file = "cryptography-41.0.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:004b6ccc95943f6a9ad3142cfabcc769d7ee38a3f60fb0dddbfb431f818c3a67"}, + {file = "cryptography-41.0.4-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:86defa8d248c3fa029da68ce61fe735432b047e32179883bdb1e79ed9bb8195e"}, + {file = "cryptography-41.0.4-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:37480760ae08065437e6573d14be973112c9e6dcaf5f11d00147ee74f37a3829"}, + {file = "cryptography-41.0.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b5f4dfe950ff0479f1f00eda09c18798d4f49b98f4e2006d644b3301682ebdca"}, + {file = "cryptography-41.0.4-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7e53db173370dea832190870e975a1e09c86a879b613948f09eb49324218c14d"}, + {file = "cryptography-41.0.4-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5b72205a360f3b6176485a333256b9bcd48700fc755fef51c8e7e67c4b63e3ac"}, + {file = "cryptography-41.0.4-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:93530900d14c37a46ce3d6c9e6fd35dbe5f5601bf6b3a5c325c7bffc030344d9"}, + {file = "cryptography-41.0.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:efc8ad4e6fc4f1752ebfb58aefece8b4e3c4cae940b0994d43649bdfce8d0d4f"}, + {file = "cryptography-41.0.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c3391bd8e6de35f6f1140e50aaeb3e2b3d6a9012536ca23ab0d9c35ec18c8a91"}, + {file = "cryptography-41.0.4-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:0d9409894f495d465fe6fda92cb70e8323e9648af912d5b9141d616df40a87b8"}, + {file = "cryptography-41.0.4-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:8ac4f9ead4bbd0bc8ab2d318f97d85147167a488be0e08814a37eb2f439d5cf6"}, + {file = "cryptography-41.0.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:047c4603aeb4bbd8db2756e38f5b8bd7e94318c047cfe4efeb5d715e08b49311"}, + {file = "cryptography-41.0.4.tar.gz", hash = "sha256:7febc3094125fc126a7f6fb1f420d0da639f3f32cb15c8ff0dc3997c4549f51a"}, ] [package.dependencies] @@ -787,7 +683,6 @@ test-randomorder = ["pytest-randomly"] name = "cytoolz" version = "0.12.2" description = "Cython implementation of Toolz: High performance functional utilities" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -896,7 +791,6 @@ cython = ["cython"] name = "decorator" version = "5.1.1" description = "Decorators for Humans" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -908,7 +802,6 @@ files = [ name = "deepmerge" version = "0.3.0" description = "a toolset to deeply merge python dictionaries." -category = "main" optional = false python-versions = ">=3" files = [ @@ -920,7 +813,6 @@ files = [ name = "deprecated" version = "1.2.14" description = "Python @deprecated decorator to deprecate old python classes, functions or methods." -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -938,7 +830,6 @@ dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "sphinx (<2)", "tox"] name = "distlib" version = "0.3.7" description = "Distribution utilities" -category = "dev" optional = false python-versions = "*" files = [ @@ -948,21 +839,19 @@ files = [ [[package]] name = "docutils" -version = "0.18.1" +version = "0.20.1" description = "Docutils -- Python Documentation Utilities" -category = "dev" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.7" files = [ - {file = "docutils-0.18.1-py2.py3-none-any.whl", hash = "sha256:23010f129180089fbcd3bc08cfefccb3b890b0050e1ca00c867036e9d161b98c"}, - {file = "docutils-0.18.1.tar.gz", hash = "sha256:679987caf361a7539d76e584cbeddc311e3aee937877c87346f31debc63e9d06"}, + {file = "docutils-0.20.1-py3-none-any.whl", hash = "sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6"}, + {file = "docutils-0.20.1.tar.gz", hash = "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b"}, ] [[package]] name = "ecdsa" version = "0.16.1" description = "ECDSA cryptographic signature library (pure python)" -category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -981,7 +870,6 @@ gmpy2 = ["gmpy2"] name = "eth-hash" version = "0.3.3" description = "eth-hash: The Ethereum hashing function, keccak256, sometimes (erroneously) called sha3" -category = "main" optional = false python-versions = ">=3.5, <4" files = [ @@ -1001,7 +889,6 @@ test = ["pytest (==5.4.1)", "pytest-xdist", "tox (==3.14.6)"] name = "eth-typing" version = "2.3.0" description = "eth-typing: Common type annotations for ethereum python packages" -category = "main" optional = false python-versions = ">=3.5, <4" files = [ @@ -1019,7 +906,6 @@ test = ["pytest (>=4.4,<4.5)", "pytest-xdist", "tox (>=2.9.1,<3)"] name = "eth-utils" version = "1.10.0" description = "eth-utils: Common utility functions for python code that interacts with Ethereum" -category = "main" optional = false python-versions = ">=3.5,!=3.5.2,<4" files = [ @@ -1043,7 +929,6 @@ test = ["hypothesis (>=4.43.0,<5.0.0)", "pytest (==5.4.1)", "pytest-xdist", "tox name = "exceptiongroup" version = "1.1.3" description = "Backport of PEP 654 (exception groups)" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1056,25 +941,24 @@ test = ["pytest (>=6)"] [[package]] name = "filelock" -version = "3.12.2" +version = "3.12.4" description = "A platform independent file lock." -category = "dev" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "filelock-3.12.2-py3-none-any.whl", hash = "sha256:cbb791cdea2a72f23da6ac5b5269ab0a0d161e9ef0100e653b69049a7706d1ec"}, - {file = "filelock-3.12.2.tar.gz", hash = "sha256:002740518d8aa59a26b0c76e10fb8c6e15eae825d34b6fdf670333fd7b938d81"}, + {file = "filelock-3.12.4-py3-none-any.whl", hash = "sha256:08c21d87ded6e2b9da6728c3dff51baf1dcecf973b768ef35bcbc3447edb9ad4"}, + {file = "filelock-3.12.4.tar.gz", hash = "sha256:2e6f249f1f3654291606e046b09f1fd5eac39b360664c27f5aad072012f8bcbd"}, ] [package.extras] -docs = ["furo (>=2023.5.20)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "diff-cover (>=7.5)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)", "pytest-timeout (>=2.1)"] +docs = ["furo (>=2023.7.26)", "sphinx (>=7.1.2)", "sphinx-autodoc-typehints (>=1.24)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.3)", "diff-cover (>=7.7)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)", "pytest-timeout (>=2.1)"] +typing = ["typing-extensions (>=4.7.1)"] [[package]] name = "frozendict" version = "2.3.8" description = "A simple immutable dictionary" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1121,7 +1005,6 @@ files = [ name = "frozenlist" version = "1.4.0" description = "A list-like structure which implements collections.abc.MutableSequence" -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1190,14 +1073,13 @@ files = [ [[package]] name = "identify" -version = "2.5.27" +version = "2.5.29" description = "File identification library for Python" -category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "identify-2.5.27-py2.py3-none-any.whl", hash = "sha256:fdb527b2dfe24602809b2201e033c2a113d7bdf716db3ca8e3243f735dcecaba"}, - {file = "identify-2.5.27.tar.gz", hash = "sha256:287b75b04a0e22d727bc9a41f0d4f3c1bcada97490fa6eabb5b28f0e9097e733"}, + {file = "identify-2.5.29-py2.py3-none-any.whl", hash = "sha256:24437fbf6f4d3fe6efd0eb9d67e24dd9106db99af5ceb27996a5f7895f24bf1b"}, + {file = "identify-2.5.29.tar.gz", hash = "sha256:d43d52b86b15918c137e3a74fff5224f60385cd0e9c38e99d07c257f02f151a5"}, ] [package.extras] @@ -1207,7 +1089,6 @@ license = ["ukkonen"] name = "idna" version = "3.4" description = "Internationalized Domain Names in Applications (IDNA)" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -1219,7 +1100,6 @@ files = [ name = "imagesize" version = "1.4.1" description = "Getting image size from png/jpeg/jpeg2000/gif file" -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -1229,23 +1109,21 @@ files = [ [[package]] name = "indy-credx" -version = "1.0.0" +version = "1.0.3" description = "" -category = "main" optional = true python-versions = ">=3.6.3" files = [ - {file = "indy_credx-1.0.0-py3-none-macosx_10_9_universal2.whl", hash = "sha256:dd936e0fb96b73fa9f1d81faca9072ea019816fdc82505b4692750262ddc6e67"}, - {file = "indy_credx-1.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:22193fcd1a7067bffde87c8aa8b0259914e39d3ccf6cba933499a11f167a0ad0"}, - {file = "indy_credx-1.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:d88438ed8c5d6c3d019a0188b9bfae26ed12942f45be02cdd979d18baaf099ee"}, - {file = "indy_credx-1.0.0-py3-none-win_amd64.whl", hash = "sha256:27101d1b3a112959c5e4edb733a005a98c43c94442486a46a7e4633824d04012"}, + {file = "indy_credx-1.0.3-py3-none-macosx_10_9_universal2.whl", hash = "sha256:91fc60edc80bad306f52640deaaa6c4e627d795f3f0df523a6ed8145b6d78a4f"}, + {file = "indy_credx-1.0.3-py3-none-manylinux2014_aarch64.whl", hash = "sha256:5c0c1170234edf9a518f4faf89767ba53f1fcf17d2f5eccb99b01623e5b4b6ba"}, + {file = "indy_credx-1.0.3-py3-none-manylinux2014_x86_64.whl", hash = "sha256:474eba89ab0674f0311ff01476f6b724ca03e37e46cce7fa729da971e3943d93"}, + {file = "indy_credx-1.0.3-py3-none-win_amd64.whl", hash = "sha256:9a120e939bda9ced7f62b678842b31911f301777887de2cec34862af95968be9"}, ] [[package]] name = "indy-vdr" version = "0.3.4" description = "" -category = "main" optional = true python-versions = ">=3.6.3" files = [ @@ -1258,7 +1136,6 @@ files = [ name = "inflection" version = "0.5.1" description = "A port of Ruby on Rails inflector to Python" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -1270,7 +1147,6 @@ files = [ name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1278,31 +1154,10 @@ files = [ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] -[[package]] -name = "jaraco-functools" -version = "3.9.0" -description = "Functools like those found in stdlib" -category = "main" -optional = false -python-versions = ">=3.8" -files = [ - {file = "jaraco.functools-3.9.0-py3-none-any.whl", hash = "sha256:df2e2b0aadd2dfcee2d7e0d7d083d5a5b68f4c8621e6915ae9819a90de65dd44"}, - {file = "jaraco.functools-3.9.0.tar.gz", hash = "sha256:8b137b0feacc17fef4bacee04c011c9e86f2341099c870a1d12d3be37b32a638"}, -] - -[package.dependencies] -more-itertools = "*" -typing-extensions = {version = "*", markers = "python_version < \"3.11\""} - -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -testing = ["jaraco.classes", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff"] - [[package]] name = "jinja2" version = "3.1.2" description = "A very fast and expressive template engine." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1320,7 +1175,6 @@ i18n = ["Babel (>=2.7)"] name = "jsonpath-ng" version = "1.5.2" description = "A final implementation of JSONPath for Python that aims to be standard compliant, including arithmetic and binary comparison operators and providing clear AST for metaprogramming." -category = "main" optional = false python-versions = "*" files = [ @@ -1337,7 +1191,6 @@ six = "*" name = "jwcrypto" version = "1.5.0" description = "Implementation of JOSE Web standards" -category = "main" optional = false python-versions = ">= 3.6" files = [ @@ -1352,7 +1205,6 @@ deprecated = "*" name = "lxml" version = "4.9.3" description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*" files = [ @@ -1460,7 +1312,6 @@ source = ["Cython (>=0.29.35)"] name = "markdown" version = "3.1.1" description = "Python implementation of Markdown." -category = "main" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" files = [ @@ -1478,7 +1329,6 @@ testing = ["coverage", "pyyaml"] name = "markupsafe" version = "2.0.1" description = "Safely add untrusted strings to HTML/XML markup." -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1557,7 +1407,6 @@ files = [ name = "marshmallow" version = "3.20.1" description = "A lightweight library for converting complex datatypes to and from native Python datatypes." -category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1578,7 +1427,6 @@ tests = ["pytest", "pytz", "simplejson"] name = "mock" version = "4.0.3" description = "Rolling backport of unittest.mock for all Pythons" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1591,96 +1439,10 @@ build = ["blurb", "twine", "wheel"] docs = ["sphinx"] test = ["pytest (<5.4)", "pytest-cov"] -[[package]] -name = "more-itertools" -version = "10.1.0" -description = "More routines for operating on iterables, beyond itertools" -category = "main" -optional = false -python-versions = ">=3.8" -files = [ - {file = "more-itertools-10.1.0.tar.gz", hash = "sha256:626c369fa0eb37bac0291bce8259b332fd59ac792fa5497b59837309cd5b114a"}, - {file = "more_itertools-10.1.0-py3-none-any.whl", hash = "sha256:64e0735fcfdc6f3464ea133afe8ea4483b1c5fe3a3d69852e6503b43a0b222e6"}, -] - -[[package]] -name = "msgpack" -version = "1.0.5" -description = "MessagePack serializer" -category = "main" -optional = false -python-versions = "*" -files = [ - {file = "msgpack-1.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:525228efd79bb831cf6830a732e2e80bc1b05436b086d4264814b4b2955b2fa9"}, - {file = "msgpack-1.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4f8d8b3bf1ff2672567d6b5c725a1b347fe838b912772aa8ae2bf70338d5a198"}, - {file = "msgpack-1.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdc793c50be3f01106245a61b739328f7dccc2c648b501e237f0699fe1395b81"}, - {file = "msgpack-1.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cb47c21a8a65b165ce29f2bec852790cbc04936f502966768e4aae9fa763cb7"}, - {file = "msgpack-1.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e42b9594cc3bf4d838d67d6ed62b9e59e201862a25e9a157019e171fbe672dd3"}, - {file = "msgpack-1.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:55b56a24893105dc52c1253649b60f475f36b3aa0fc66115bffafb624d7cb30b"}, - {file = "msgpack-1.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:1967f6129fc50a43bfe0951c35acbb729be89a55d849fab7686004da85103f1c"}, - {file = "msgpack-1.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20a97bf595a232c3ee6d57ddaadd5453d174a52594bf9c21d10407e2a2d9b3bd"}, - {file = "msgpack-1.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d25dd59bbbbb996eacf7be6b4ad082ed7eacc4e8f3d2df1ba43822da9bfa122a"}, - {file = "msgpack-1.0.5-cp310-cp310-win32.whl", hash = "sha256:382b2c77589331f2cb80b67cc058c00f225e19827dbc818d700f61513ab47bea"}, - {file = "msgpack-1.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:4867aa2df9e2a5fa5f76d7d5565d25ec76e84c106b55509e78c1ede0f152659a"}, - {file = "msgpack-1.0.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9f5ae84c5c8a857ec44dc180a8b0cc08238e021f57abdf51a8182e915e6299f0"}, - {file = "msgpack-1.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9e6ca5d5699bcd89ae605c150aee83b5321f2115695e741b99618f4856c50898"}, - {file = "msgpack-1.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5494ea30d517a3576749cad32fa27f7585c65f5f38309c88c6d137877fa28a5a"}, - {file = "msgpack-1.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ab2f3331cb1b54165976a9d976cb251a83183631c88076613c6c780f0d6e45a"}, - {file = "msgpack-1.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28592e20bbb1620848256ebc105fc420436af59515793ed27d5c77a217477705"}, - {file = "msgpack-1.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe5c63197c55bce6385d9aee16c4d0641684628f63ace85f73571e65ad1c1e8d"}, - {file = "msgpack-1.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ed40e926fa2f297e8a653c954b732f125ef97bdd4c889f243182299de27e2aa9"}, - {file = "msgpack-1.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b2de4c1c0538dcb7010902a2b97f4e00fc4ddf2c8cda9749af0e594d3b7fa3d7"}, - {file = "msgpack-1.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bf22a83f973b50f9d38e55c6aade04c41ddda19b00c4ebc558930d78eecc64ed"}, - {file = "msgpack-1.0.5-cp311-cp311-win32.whl", hash = "sha256:c396e2cc213d12ce017b686e0f53497f94f8ba2b24799c25d913d46c08ec422c"}, - {file = "msgpack-1.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:6c4c68d87497f66f96d50142a2b73b97972130d93677ce930718f68828b382e2"}, - {file = "msgpack-1.0.5-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a2b031c2e9b9af485d5e3c4520f4220d74f4d222a5b8dc8c1a3ab9448ca79c57"}, - {file = "msgpack-1.0.5-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f837b93669ce4336e24d08286c38761132bc7ab29782727f8557e1eb21b2080"}, - {file = "msgpack-1.0.5-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1d46dfe3832660f53b13b925d4e0fa1432b00f5f7210eb3ad3bb9a13c6204a6"}, - {file = "msgpack-1.0.5-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:366c9a7b9057e1547f4ad51d8facad8b406bab69c7d72c0eb6f529cf76d4b85f"}, - {file = "msgpack-1.0.5-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:4c075728a1095efd0634a7dccb06204919a2f67d1893b6aa8e00497258bf926c"}, - {file = "msgpack-1.0.5-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:f933bbda5a3ee63b8834179096923b094b76f0c7a73c1cfe8f07ad608c58844b"}, - {file = "msgpack-1.0.5-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:36961b0568c36027c76e2ae3ca1132e35123dcec0706c4b7992683cc26c1320c"}, - {file = "msgpack-1.0.5-cp36-cp36m-win32.whl", hash = "sha256:b5ef2f015b95f912c2fcab19c36814963b5463f1fb9049846994b007962743e9"}, - {file = "msgpack-1.0.5-cp36-cp36m-win_amd64.whl", hash = "sha256:288e32b47e67f7b171f86b030e527e302c91bd3f40fd9033483f2cacc37f327a"}, - {file = "msgpack-1.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:137850656634abddfb88236008339fdaba3178f4751b28f270d2ebe77a563b6c"}, - {file = "msgpack-1.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c05a4a96585525916b109bb85f8cb6511db1c6f5b9d9cbcbc940dc6b4be944b"}, - {file = "msgpack-1.0.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56a62ec00b636583e5cb6ad313bbed36bb7ead5fa3a3e38938503142c72cba4f"}, - {file = "msgpack-1.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef8108f8dedf204bb7b42994abf93882da1159728a2d4c5e82012edd92c9da9f"}, - {file = "msgpack-1.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1835c84d65f46900920b3708f5ba829fb19b1096c1800ad60bae8418652a951d"}, - {file = "msgpack-1.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:e57916ef1bd0fee4f21c4600e9d1da352d8816b52a599c46460e93a6e9f17086"}, - {file = "msgpack-1.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:17358523b85973e5f242ad74aa4712b7ee560715562554aa2134d96e7aa4cbbf"}, - {file = "msgpack-1.0.5-cp37-cp37m-win32.whl", hash = "sha256:cb5aaa8c17760909ec6cb15e744c3ebc2ca8918e727216e79607b7bbce9c8f77"}, - {file = "msgpack-1.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:ab31e908d8424d55601ad7075e471b7d0140d4d3dd3272daf39c5c19d936bd82"}, - {file = "msgpack-1.0.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b72d0698f86e8d9ddf9442bdedec15b71df3598199ba33322d9711a19f08145c"}, - {file = "msgpack-1.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:379026812e49258016dd84ad79ac8446922234d498058ae1d415f04b522d5b2d"}, - {file = "msgpack-1.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:332360ff25469c346a1c5e47cbe2a725517919892eda5cfaffe6046656f0b7bb"}, - {file = "msgpack-1.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:476a8fe8fae289fdf273d6d2a6cb6e35b5a58541693e8f9f019bfe990a51e4ba"}, - {file = "msgpack-1.0.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9985b214f33311df47e274eb788a5893a761d025e2b92c723ba4c63936b69b1"}, - {file = "msgpack-1.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48296af57cdb1d885843afd73c4656be5c76c0c6328db3440c9601a98f303d87"}, - {file = "msgpack-1.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:addab7e2e1fcc04bd08e4eb631c2a90960c340e40dfc4a5e24d2ff0d5a3b3edb"}, - {file = "msgpack-1.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:916723458c25dfb77ff07f4c66aed34e47503b2eb3188b3adbec8d8aa6e00f48"}, - {file = "msgpack-1.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:821c7e677cc6acf0fd3f7ac664c98803827ae6de594a9f99563e48c5a2f27eb0"}, - {file = "msgpack-1.0.5-cp38-cp38-win32.whl", hash = "sha256:1c0f7c47f0087ffda62961d425e4407961a7ffd2aa004c81b9c07d9269512f6e"}, - {file = "msgpack-1.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:bae7de2026cbfe3782c8b78b0db9cbfc5455e079f1937cb0ab8d133496ac55e1"}, - {file = "msgpack-1.0.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:20c784e66b613c7f16f632e7b5e8a1651aa5702463d61394671ba07b2fc9e025"}, - {file = "msgpack-1.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:266fa4202c0eb94d26822d9bfd7af25d1e2c088927fe8de9033d929dd5ba24c5"}, - {file = "msgpack-1.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:18334484eafc2b1aa47a6d42427da7fa8f2ab3d60b674120bce7a895a0a85bdd"}, - {file = "msgpack-1.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57e1f3528bd95cc44684beda696f74d3aaa8a5e58c816214b9046512240ef437"}, - {file = "msgpack-1.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:586d0d636f9a628ddc6a17bfd45aa5b5efaf1606d2b60fa5d87b8986326e933f"}, - {file = "msgpack-1.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a740fa0e4087a734455f0fc3abf5e746004c9da72fbd541e9b113013c8dc3282"}, - {file = "msgpack-1.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3055b0455e45810820db1f29d900bf39466df96ddca11dfa6d074fa47054376d"}, - {file = "msgpack-1.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:a61215eac016f391129a013c9e46f3ab308db5f5ec9f25811e811f96962599a8"}, - {file = "msgpack-1.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:362d9655cd369b08fda06b6657a303eb7172d5279997abe094512e919cf74b11"}, - {file = "msgpack-1.0.5-cp39-cp39-win32.whl", hash = "sha256:ac9dd47af78cae935901a9a500104e2dea2e253207c924cc95de149606dc43cc"}, - {file = "msgpack-1.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:06f5174b5f8ed0ed919da0e62cbd4ffde676a374aba4020034da05fab67b9164"}, - {file = "msgpack-1.0.5.tar.gz", hash = "sha256:c075544284eadc5cddc70f4757331d99dcbc16b2bbd4849d15f8aae4cf36d31c"}, -] - [[package]] name = "multidict" version = "6.0.4" description = "multidict implementation" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1764,7 +1526,6 @@ files = [ name = "multiformats" version = "0.2.1" description = "Python implementation of multiformats protocols." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1786,7 +1547,6 @@ full = ["blake3", "mmh3", "pycryptodomex", "pysha3", "pyskein"] name = "multiformats-config" version = "0.2.0.post4" description = "Pre-loading configuration module for the 'multiformats' package." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1804,7 +1564,6 @@ dev = ["mypy", "pylint", "pytest", "pytest-cov"] name = "mypy-extensions" version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -1814,21 +1573,19 @@ files = [ [[package]] name = "nest-asyncio" -version = "1.5.7" +version = "1.5.8" description = "Patch asyncio to allow nested event loops" -category = "main" optional = false python-versions = ">=3.5" files = [ - {file = "nest_asyncio-1.5.7-py3-none-any.whl", hash = "sha256:5301c82941b550b3123a1ea772ba9a1c80bad3a182be8c1a5ae6ad3be57a9657"}, - {file = "nest_asyncio-1.5.7.tar.gz", hash = "sha256:6a80f7b98f24d9083ed24608977c09dd608d83f91cccc24c9d2cba6d10e01c10"}, + {file = "nest_asyncio-1.5.8-py3-none-any.whl", hash = "sha256:accda7a339a70599cb08f9dd09a67e0c2ef8d8d6f4c07f96ab203f2ae254e48d"}, + {file = "nest_asyncio-1.5.8.tar.gz", hash = "sha256:25aa2ca0d2a5b5531956b9e273b45cf664cae2b145101d73b86b199978d48fdb"}, ] [[package]] name = "nodeenv" version = "1.8.0" description = "Node.js virtual environment builder" -category = "dev" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" files = [ @@ -1843,7 +1600,6 @@ setuptools = "*" name = "packaging" version = "23.1" description = "Core utilities for Python packages" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1855,7 +1611,6 @@ files = [ name = "pathspec" version = "0.11.2" description = "Utility library for gitignore style pattern matching of file paths." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1867,7 +1622,6 @@ files = [ name = "peerdid" version = "0.5.2" description = "PeerDID for Python" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1885,68 +1639,65 @@ tests = ["pytest (==6.2.5)", "pytest-xdist (==2.3.0)"] [[package]] name = "pillow" -version = "10.0.0" +version = "10.0.1" description = "Python Imaging Library (Fork)" -category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "Pillow-10.0.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1f62406a884ae75fb2f818694469519fb685cc7eaff05d3451a9ebe55c646891"}, - {file = "Pillow-10.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d5db32e2a6ccbb3d34d87c87b432959e0db29755727afb37290e10f6e8e62614"}, - {file = "Pillow-10.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edf4392b77bdc81f36e92d3a07a5cd072f90253197f4a52a55a8cec48a12483b"}, - {file = "Pillow-10.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:520f2a520dc040512699f20fa1c363eed506e94248d71f85412b625026f6142c"}, - {file = "Pillow-10.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:8c11160913e3dd06c8ffdb5f233a4f254cb449f4dfc0f8f4549eda9e542c93d1"}, - {file = "Pillow-10.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a74ba0c356aaa3bb8e3eb79606a87669e7ec6444be352870623025d75a14a2bf"}, - {file = "Pillow-10.0.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d5d0dae4cfd56969d23d94dc8e89fb6a217be461c69090768227beb8ed28c0a3"}, - {file = "Pillow-10.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:22c10cc517668d44b211717fd9775799ccec4124b9a7f7b3635fc5386e584992"}, - {file = "Pillow-10.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:dffe31a7f47b603318c609f378ebcd57f1554a3a6a8effbc59c3c69f804296de"}, - {file = "Pillow-10.0.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:9fb218c8a12e51d7ead2a7c9e101a04982237d4855716af2e9499306728fb485"}, - {file = "Pillow-10.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d35e3c8d9b1268cbf5d3670285feb3528f6680420eafe35cccc686b73c1e330f"}, - {file = "Pillow-10.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ed64f9ca2f0a95411e88a4efbd7a29e5ce2cea36072c53dd9d26d9c76f753b3"}, - {file = "Pillow-10.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b6eb5502f45a60a3f411c63187db83a3d3107887ad0d036c13ce836f8a36f1d"}, - {file = "Pillow-10.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:c1fbe7621c167ecaa38ad29643d77a9ce7311583761abf7836e1510c580bf3dd"}, - {file = "Pillow-10.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:cd25d2a9d2b36fcb318882481367956d2cf91329f6892fe5d385c346c0649629"}, - {file = "Pillow-10.0.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:3b08d4cc24f471b2c8ca24ec060abf4bebc6b144cb89cba638c720546b1cf538"}, - {file = "Pillow-10.0.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d737a602fbd82afd892ca746392401b634e278cb65d55c4b7a8f48e9ef8d008d"}, - {file = "Pillow-10.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:3a82c40d706d9aa9734289740ce26460a11aeec2d9c79b7af87bb35f0073c12f"}, - {file = "Pillow-10.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:bc2ec7c7b5d66b8ec9ce9f720dbb5fa4bace0f545acd34870eff4a369b44bf37"}, - {file = "Pillow-10.0.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:d80cf684b541685fccdd84c485b31ce73fc5c9b5d7523bf1394ce134a60c6883"}, - {file = "Pillow-10.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:76de421f9c326da8f43d690110f0e79fe3ad1e54be811545d7d91898b4c8493e"}, - {file = "Pillow-10.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81ff539a12457809666fef6624684c008e00ff6bf455b4b89fd00a140eecd640"}, - {file = "Pillow-10.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce543ed15570eedbb85df19b0a1a7314a9c8141a36ce089c0a894adbfccb4568"}, - {file = "Pillow-10.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:685ac03cc4ed5ebc15ad5c23bc555d68a87777586d970c2c3e216619a5476223"}, - {file = "Pillow-10.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:d72e2ecc68a942e8cf9739619b7f408cc7b272b279b56b2c83c6123fcfa5cdff"}, - {file = "Pillow-10.0.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d50b6aec14bc737742ca96e85d6d0a5f9bfbded018264b3b70ff9d8c33485551"}, - {file = "Pillow-10.0.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:00e65f5e822decd501e374b0650146063fbb30a7264b4d2744bdd7b913e0cab5"}, - {file = "Pillow-10.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:f31f9fdbfecb042d046f9d91270a0ba28368a723302786c0009ee9b9f1f60199"}, - {file = "Pillow-10.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:1ce91b6ec08d866b14413d3f0bbdea7e24dfdc8e59f562bb77bc3fe60b6144ca"}, - {file = "Pillow-10.0.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:349930d6e9c685c089284b013478d6f76e3a534e36ddfa912cde493f235372f3"}, - {file = "Pillow-10.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3a684105f7c32488f7153905a4e3015a3b6c7182e106fe3c37fbb5ef3e6994c3"}, - {file = "Pillow-10.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4f69b3700201b80bb82c3a97d5e9254084f6dd5fb5b16fc1a7b974260f89f43"}, - {file = "Pillow-10.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f07ea8d2f827d7d2a49ecf1639ec02d75ffd1b88dcc5b3a61bbb37a8759ad8d"}, - {file = "Pillow-10.0.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:040586f7d37b34547153fa383f7f9aed68b738992380ac911447bb78f2abe530"}, - {file = "Pillow-10.0.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:f88a0b92277de8e3ca715a0d79d68dc82807457dae3ab8699c758f07c20b3c51"}, - {file = "Pillow-10.0.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c7cf14a27b0d6adfaebb3ae4153f1e516df54e47e42dcc073d7b3d76111a8d86"}, - {file = "Pillow-10.0.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3400aae60685b06bb96f99a21e1ada7bc7a413d5f49bce739828ecd9391bb8f7"}, - {file = "Pillow-10.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:dbc02381779d412145331789b40cc7b11fdf449e5d94f6bc0b080db0a56ea3f0"}, - {file = "Pillow-10.0.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:9211e7ad69d7c9401cfc0e23d49b69ca65ddd898976d660a2fa5904e3d7a9baa"}, - {file = "Pillow-10.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:faaf07ea35355b01a35cb442dd950d8f1bb5b040a7787791a535de13db15ed90"}, - {file = "Pillow-10.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9f72a021fbb792ce98306ffb0c348b3c9cb967dce0f12a49aa4c3d3fdefa967"}, - {file = "Pillow-10.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f7c16705f44e0504a3a2a14197c1f0b32a95731d251777dcb060aa83022cb2d"}, - {file = "Pillow-10.0.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:76edb0a1fa2b4745fb0c99fb9fb98f8b180a1bbceb8be49b087e0b21867e77d3"}, - {file = "Pillow-10.0.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:368ab3dfb5f49e312231b6f27b8820c823652b7cd29cfbd34090565a015e99ba"}, - {file = "Pillow-10.0.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:608bfdee0d57cf297d32bcbb3c728dc1da0907519d1784962c5f0c68bb93e5a3"}, - {file = "Pillow-10.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5c6e3df6bdd396749bafd45314871b3d0af81ff935b2d188385e970052091017"}, - {file = "Pillow-10.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:7be600823e4c8631b74e4a0d38384c73f680e6105a7d3c6824fcf226c178c7e6"}, - {file = "Pillow-10.0.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:92be919bbc9f7d09f7ae343c38f5bb21c973d2576c1d45600fce4b74bafa7ac0"}, - {file = "Pillow-10.0.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8182b523b2289f7c415f589118228d30ac8c355baa2f3194ced084dac2dbba"}, - {file = "Pillow-10.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:38250a349b6b390ee6047a62c086d3817ac69022c127f8a5dc058c31ccef17f3"}, - {file = "Pillow-10.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:88af2003543cc40c80f6fca01411892ec52b11021b3dc22ec3bc9d5afd1c5334"}, - {file = "Pillow-10.0.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:c189af0545965fa8d3b9613cfdb0cd37f9d71349e0f7750e1fd704648d475ed2"}, - {file = "Pillow-10.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce7b031a6fc11365970e6a5686d7ba8c63e4c1cf1ea143811acbb524295eabed"}, - {file = "Pillow-10.0.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:db24668940f82321e746773a4bc617bfac06ec831e5c88b643f91f122a785684"}, - {file = "Pillow-10.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:efe8c0681042536e0d06c11f48cebe759707c9e9abf880ee213541c5b46c5bf3"}, - {file = "Pillow-10.0.0.tar.gz", hash = "sha256:9c82b5b3e043c7af0d95792d0d20ccf68f61a1fec6b3530e718b688422727396"}, + {file = "Pillow-10.0.1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:8f06be50669087250f319b706decf69ca71fdecd829091a37cc89398ca4dc17a"}, + {file = "Pillow-10.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:50bd5f1ebafe9362ad622072a1d2f5850ecfa44303531ff14353a4059113b12d"}, + {file = "Pillow-10.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6a90167bcca1216606223a05e2cf991bb25b14695c518bc65639463d7db722d"}, + {file = "Pillow-10.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f11c9102c56ffb9ca87134bd025a43d2aba3f1155f508eff88f694b33a9c6d19"}, + {file = "Pillow-10.0.1-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:186f7e04248103482ea6354af6d5bcedb62941ee08f7f788a1c7707bc720c66f"}, + {file = "Pillow-10.0.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:0462b1496505a3462d0f35dc1c4d7b54069747d65d00ef48e736acda2c8cbdff"}, + {file = "Pillow-10.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d889b53ae2f030f756e61a7bff13684dcd77e9af8b10c6048fb2c559d6ed6eaf"}, + {file = "Pillow-10.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:552912dbca585b74d75279a7570dd29fa43b6d93594abb494ebb31ac19ace6bd"}, + {file = "Pillow-10.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:787bb0169d2385a798888e1122c980c6eff26bf941a8ea79747d35d8f9210ca0"}, + {file = "Pillow-10.0.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:fd2a5403a75b54661182b75ec6132437a181209b901446ee5724b589af8edef1"}, + {file = "Pillow-10.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2d7e91b4379f7a76b31c2dda84ab9e20c6220488e50f7822e59dac36b0cd92b1"}, + {file = "Pillow-10.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19e9adb3f22d4c416e7cd79b01375b17159d6990003633ff1d8377e21b7f1b21"}, + {file = "Pillow-10.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93139acd8109edcdeffd85e3af8ae7d88b258b3a1e13a038f542b79b6d255c54"}, + {file = "Pillow-10.0.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:92a23b0431941a33242b1f0ce6c88a952e09feeea9af4e8be48236a68ffe2205"}, + {file = "Pillow-10.0.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:cbe68deb8580462ca0d9eb56a81912f59eb4542e1ef8f987405e35a0179f4ea2"}, + {file = "Pillow-10.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:522ff4ac3aaf839242c6f4e5b406634bfea002469656ae8358644fc6c4856a3b"}, + {file = "Pillow-10.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:84efb46e8d881bb06b35d1d541aa87f574b58e87f781cbba8d200daa835b42e1"}, + {file = "Pillow-10.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:898f1d306298ff40dc1b9ca24824f0488f6f039bc0e25cfb549d3195ffa17088"}, + {file = "Pillow-10.0.1-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:bcf1207e2f2385a576832af02702de104be71301c2696d0012b1b93fe34aaa5b"}, + {file = "Pillow-10.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5d6c9049c6274c1bb565021367431ad04481ebb54872edecfcd6088d27edd6ed"}, + {file = "Pillow-10.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28444cb6ad49726127d6b340217f0627abc8732f1194fd5352dec5e6a0105635"}, + {file = "Pillow-10.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de596695a75496deb3b499c8c4f8e60376e0516e1a774e7bc046f0f48cd620ad"}, + {file = "Pillow-10.0.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:2872f2d7846cf39b3dbff64bc1104cc48c76145854256451d33c5faa55c04d1a"}, + {file = "Pillow-10.0.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:4ce90f8a24e1c15465048959f1e94309dfef93af272633e8f37361b824532e91"}, + {file = "Pillow-10.0.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ee7810cf7c83fa227ba9125de6084e5e8b08c59038a7b2c9045ef4dde61663b4"}, + {file = "Pillow-10.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b1be1c872b9b5fcc229adeadbeb51422a9633abd847c0ff87dc4ef9bb184ae08"}, + {file = "Pillow-10.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:98533fd7fa764e5f85eebe56c8e4094db912ccbe6fbf3a58778d543cadd0db08"}, + {file = "Pillow-10.0.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:764d2c0daf9c4d40ad12fbc0abd5da3af7f8aa11daf87e4fa1b834000f4b6b0a"}, + {file = "Pillow-10.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fcb59711009b0168d6ee0bd8fb5eb259c4ab1717b2f538bbf36bacf207ef7a68"}, + {file = "Pillow-10.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:697a06bdcedd473b35e50a7e7506b1d8ceb832dc238a336bd6f4f5aa91a4b500"}, + {file = "Pillow-10.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f665d1e6474af9f9da5e86c2a3a2d2d6204e04d5af9c06b9d42afa6ebde3f21"}, + {file = "Pillow-10.0.1-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:2fa6dd2661838c66f1a5473f3b49ab610c98a128fc08afbe81b91a1f0bf8c51d"}, + {file = "Pillow-10.0.1-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:3a04359f308ebee571a3127fdb1bd01f88ba6f6fb6d087f8dd2e0d9bff43f2a7"}, + {file = "Pillow-10.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:723bd25051454cea9990203405fa6b74e043ea76d4968166dfd2569b0210886a"}, + {file = "Pillow-10.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:71671503e3015da1b50bd18951e2f9daf5b6ffe36d16f1eb2c45711a301521a7"}, + {file = "Pillow-10.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:44e7e4587392953e5e251190a964675f61e4dae88d1e6edbe9f36d6243547ff3"}, + {file = "Pillow-10.0.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:3855447d98cced8670aaa63683808df905e956f00348732448b5a6df67ee5849"}, + {file = "Pillow-10.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ed2d9c0704f2dc4fa980b99d565c0c9a543fe5101c25b3d60488b8ba80f0cce1"}, + {file = "Pillow-10.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5bb289bb835f9fe1a1e9300d011eef4d69661bb9b34d5e196e5e82c4cb09b37"}, + {file = "Pillow-10.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a0d3e54ab1df9df51b914b2233cf779a5a10dfd1ce339d0421748232cea9876"}, + {file = "Pillow-10.0.1-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:2cc6b86ece42a11f16f55fe8903595eff2b25e0358dec635d0a701ac9586588f"}, + {file = "Pillow-10.0.1-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:ca26ba5767888c84bf5a0c1a32f069e8204ce8c21d00a49c90dabeba00ce0145"}, + {file = "Pillow-10.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f0b4b06da13275bc02adfeb82643c4a6385bd08d26f03068c2796f60d125f6f2"}, + {file = "Pillow-10.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bc2e3069569ea9dbe88d6b8ea38f439a6aad8f6e7a6283a38edf61ddefb3a9bf"}, + {file = "Pillow-10.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:8b451d6ead6e3500b6ce5c7916a43d8d8d25ad74b9102a629baccc0808c54971"}, + {file = "Pillow-10.0.1-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:32bec7423cdf25c9038fef614a853c9d25c07590e1a870ed471f47fb80b244db"}, + {file = "Pillow-10.0.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7cf63d2c6928b51d35dfdbda6f2c1fddbe51a6bc4a9d4ee6ea0e11670dd981e"}, + {file = "Pillow-10.0.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f6d3d4c905e26354e8f9d82548475c46d8e0889538cb0657aa9c6f0872a37aa4"}, + {file = "Pillow-10.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:847e8d1017c741c735d3cd1883fa7b03ded4f825a6e5fcb9378fd813edee995f"}, + {file = "Pillow-10.0.1-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:7f771e7219ff04b79e231d099c0a28ed83aa82af91fd5fa9fdb28f5b8d5addaf"}, + {file = "Pillow-10.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:459307cacdd4138edee3875bbe22a2492519e060660eaf378ba3b405d1c66317"}, + {file = "Pillow-10.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b059ac2c4c7a97daafa7dc850b43b2d3667def858a4f112d1aa082e5c3d6cf7d"}, + {file = "Pillow-10.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:d6caf3cd38449ec3cd8a68b375e0c6fe4b6fd04edb6c9766b55ef84a6e8ddf2d"}, + {file = "Pillow-10.0.1.tar.gz", hash = "sha256:d72967b06be9300fed5cfbc8b5bafceec48bf7cdc7dab66b1d2549035287191d"}, ] [package.extras] @@ -1957,7 +1708,6 @@ tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "pa name = "platformdirs" version = "3.10.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1973,7 +1723,6 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-co name = "pluggy" version = "1.3.0" description = "plugin and hook calling mechanisms for python" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1989,7 +1738,6 @@ testing = ["pytest", "pytest-benchmark"] name = "ply" version = "3.11" description = "Python Lex & Yacc" -category = "main" optional = false python-versions = "*" files = [ @@ -2001,7 +1749,6 @@ files = [ name = "portalocker" version = "2.7.0" description = "Wraps the portalocker recipe for easy usage" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -2021,7 +1768,6 @@ tests = ["pytest (>=5.4.1)", "pytest-cov (>=2.8.1)", "pytest-mypy (>=0.8.0)", "p name = "pre-commit" version = "3.3.3" description = "A framework for managing and maintaining multi-language pre-commit hooks." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -2040,9 +1786,8 @@ virtualenv = ">=20.10.0" name = "prompt-toolkit" version = "2.0.10" description = "Library for building powerful interactive command lines in Python" -category = "main" optional = false -python-versions = ">=2.6,<3.0.0 || >=3.3.0" +python-versions = ">=2.6,<3.0.dev0 || >=3.3.dev0" files = [ {file = "prompt_toolkit-2.0.10-py2-none-any.whl", hash = "sha256:e7f8af9e3d70f514373bf41aa51bc33af12a6db3f71461ea47fea985defb2c31"}, {file = "prompt_toolkit-2.0.10-py3-none-any.whl", hash = "sha256:46642344ce457641f28fc9d1c9ca939b63dadf8df128b86f1b9860e59c73a5e4"}, @@ -2057,7 +1802,6 @@ wcwidth = "*" name = "ptvsd" version = "4.3.2" description = "Remote debugging server for Python support in Visual Studio and Visual Studio Code" -category = "dev" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" files = [ @@ -2092,7 +1836,6 @@ files = [ name = "pycparser" version = "2.21" description = "C parser in Python" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -2104,7 +1847,6 @@ files = [ name = "pydantic" version = "1.9.2" description = "Data validation and settings management using python type hints" -category = "main" optional = false python-versions = ">=3.6.1" files = [ @@ -2156,7 +1898,6 @@ email = ["email-validator (>=1.0.3)"] name = "pydevd" version = "1.5.1" description = "PyDev.Debugger (used in PyDev, PyCharm and VSCode Python)" -category = "dev" optional = false python-versions = "*" files = [ @@ -2177,7 +1918,6 @@ files = [ name = "pydevd-pycharm" version = "193.6015.41" description = "PyCharm Debugger (used in PyCharm and PyDev)" -category = "dev" optional = false python-versions = "*" files = [ @@ -2186,14 +1926,13 @@ files = [ [[package]] name = "pydid" -version = "0.3.8" +version = "0.3.9" description = "Python library for validating, constructing, and representing DIDs and DID Documents" -category = "main" optional = false python-versions = ">=3.6.9,<4.0.0" files = [ - {file = "pydid-0.3.8-py3-none-any.whl", hash = "sha256:53734a20962418aeaaaef8797a6b026835bcbcfcc08afd55c12e4d90c94493bb"}, - {file = "pydid-0.3.8.tar.gz", hash = "sha256:1eec0d9b45df2bcb1a62499bfbd52245fb77207dff5ef34029fb13559a6ac3d3"}, + {file = "pydid-0.3.9-py3-none-any.whl", hash = "sha256:5e4ce9825f2c17500e7f3228c463c8a1a9f1591432ba05b8295c582ebffcb3d8"}, + {file = "pydid-0.3.9.tar.gz", hash = "sha256:1ab44576b5711386af43936ea79ebd5202edea76a2003fab590ba21b6e6a335d"}, ] [package.dependencies] @@ -2205,7 +1944,6 @@ typing-extensions = ">=4.0.0,<4.1.0" name = "pygments" version = "2.16.1" description = "Pygments is a syntax highlighting package written in Python." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2220,7 +1958,6 @@ plugins = ["importlib-metadata"] name = "pyjwt" version = "2.8.0" description = "JSON Web Token implementation in Python" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2238,7 +1975,6 @@ tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] name = "pyld" version = "2.0.3" description = "Python implementation of the JSON-LD API" -category = "main" optional = false python-versions = "*" files = [ @@ -2260,7 +1996,6 @@ requests = ["requests"] name = "pynacl" version = "1.5.0" description = "Python binding to the Networking and Cryptography (NaCl) library" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -2287,7 +2022,6 @@ tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"] name = "pytest" version = "7.4.2" description = "pytest: simple powerful testing with Python" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2310,7 +2044,6 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no name = "pytest-asyncio" version = "0.14.0" description = "Pytest support for asyncio." -category = "dev" optional = false python-versions = ">= 3.5" files = [ @@ -2328,7 +2061,6 @@ testing = ["async-generator (>=1.3)", "coverage", "hypothesis (>=5.7.1)"] name = "pytest-cov" version = "2.10.1" description = "Pytest plugin for measuring coverage." -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -2347,7 +2079,6 @@ testing = ["fields", "hunter", "process-tests (==2.0.2)", "pytest-xdist", "six", name = "pytest-ruff" version = "0.1.1" description = "pytest plugin to check ruff requirements." -category = "dev" optional = false python-versions = ">=3.7,<4.0" files = [ @@ -2362,7 +2093,6 @@ ruff = ">=0.0.242" name = "python-dateutil" version = "2.8.2" description = "Extensions to the standard Python datetime module" -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ @@ -2377,7 +2107,6 @@ six = ">=1.5" name = "python-json-logger" version = "2.0.7" description = "A python library adding a json log formatter" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -2389,7 +2118,6 @@ files = [ name = "python3-indy" version = "1.16.0.post286" description = "This is the official SDK for Hyperledger Indy (https://www.hyperledger.org/projects), which provides a distributed-ledger-based foundation for self-sovereign identity (https://sovrin.org). The major artifact of the SDK is a c-callable library." -category = "main" optional = true python-versions = "*" files = [ @@ -2406,7 +2134,6 @@ test = ["base58", "pytest (<3.7)", "pytest-asyncio (==0.10.0)"] name = "pytz" version = "2021.1" description = "World timezone definitions, modern and historical" -category = "main" optional = false python-versions = "*" files = [ @@ -2418,7 +2145,6 @@ files = [ name = "pywin32" version = "306" description = "Python for Window Extensions" -category = "main" optional = false python-versions = "*" files = [ @@ -2442,7 +2168,6 @@ files = [ name = "pyyaml" version = "6.0.1" description = "YAML parser and emitter for Python" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -2451,6 +2176,7 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -2458,8 +2184,15 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -2476,6 +2209,7 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -2483,6 +2217,7 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -2492,7 +2227,6 @@ files = [ name = "qrcode" version = "6.1" description = "QR Code image generator" -category = "main" optional = false python-versions = "*" files = [ @@ -2515,7 +2249,6 @@ test = ["mock", "pytest", "pytest-cov"] name = "requests" version = "2.31.0" description = "Python HTTP for Humans." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2537,7 +2270,6 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] name = "rlp" version = "1.2.0" description = "A package for Recursive Length Prefix encoding and decoding" -category = "main" optional = false python-versions = "*" files = [ @@ -2558,7 +2290,6 @@ test = ["hypothesis (==3.56.5)", "pytest (==3.3.2)", "tox (>=2.9.1,<3)"] name = "ruff" version = "0.0.285" description = "An extremely fast Python linter, written in Rust." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2585,7 +2316,6 @@ files = [ name = "sd-jwt" version = "0.9.1" description = "The reference implementation of the IETF SD-JWT specification." -category = "main" optional = false python-versions = "^3.8" files = [] @@ -2603,26 +2333,24 @@ resolved_reference = "0d857bf9c4971d27f1e716814ba8b55d323caaaf" [[package]] name = "setuptools" -version = "68.2.0" +version = "68.2.2" description = "Easily download, build, install, upgrade, and uninstall Python packages" -category = "main" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-68.2.0-py3-none-any.whl", hash = "sha256:af3d5949030c3f493f550876b2fd1dd5ec66689c4ee5d5344f009746f71fd5a8"}, - {file = "setuptools-68.2.0.tar.gz", hash = "sha256:00478ca80aeebeecb2f288d3206b0de568df5cd2b8fada1209843cc9a8d88a48"}, + {file = "setuptools-68.2.2-py3-none-any.whl", hash = "sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a"}, + {file = "setuptools-68.2.2.tar.gz", hash = "sha256:4ac1475276d2f1c48684874089fefcd83bd7162ddaafb81fac866ba0db282a87"}, ] [package.extras] docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "simplejson" version = "3.19.1" description = "Simple, fast, extensible JSON encoder/decoder for Python" -category = "main" optional = false python-versions = ">=2.5, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -2717,7 +2445,6 @@ files = [ name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" -category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -2729,7 +2456,6 @@ files = [ name = "snowballstemmer" version = "2.2.0" description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." -category = "dev" optional = false python-versions = "*" files = [ @@ -2741,7 +2467,6 @@ files = [ name = "sphinx" version = "1.8.4" description = "Python documentation generator" -category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -2770,44 +2495,25 @@ websupport = ["sqlalchemy (>=0.9)", "whoosh (>=2.0)"] [[package]] name = "sphinx-rtd-theme" -version = "1.3.0" +version = "0.5.1" description = "Read the Docs theme for Sphinx" -category = "dev" optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +python-versions = "*" files = [ - {file = "sphinx_rtd_theme-1.3.0-py2.py3-none-any.whl", hash = "sha256:46ddef89cc2416a81ecfbeaceab1881948c014b1b6e4450b815311a89fb977b0"}, - {file = "sphinx_rtd_theme-1.3.0.tar.gz", hash = "sha256:590b030c7abb9cf038ec053b95e5380b5c70d61591eb0b552063fbe7c41f0931"}, + {file = "sphinx_rtd_theme-0.5.1-py2.py3-none-any.whl", hash = "sha256:fa6bebd5ab9a73da8e102509a86f3fcc36dec04a0b52ea80e5a033b2aba00113"}, + {file = "sphinx_rtd_theme-0.5.1.tar.gz", hash = "sha256:eda689eda0c7301a80cf122dad28b1861e5605cbf455558f3775e1e8200e83a5"}, ] [package.dependencies] -docutils = "<0.19" -sphinx = ">=1.6,<8" -sphinxcontrib-jquery = ">=4,<5" +sphinx = "*" [package.extras] -dev = ["bump2version", "sphinxcontrib-httpdomain", "transifex-client", "wheel"] - -[[package]] -name = "sphinxcontrib-jquery" -version = "4.1" -description = "Extension to include jQuery on newer Sphinx releases" -category = "dev" -optional = false -python-versions = ">=2.7" -files = [ - {file = "sphinxcontrib-jquery-4.1.tar.gz", hash = "sha256:1620739f04e36a2c779f1a131a2dfd49b2fd07351bf1968ced074365933abc7a"}, - {file = "sphinxcontrib_jquery-4.1-py2.py3-none-any.whl", hash = "sha256:f936030d7d0147dd026a4f2b5a57343d233f1fc7b363f68b3d4f1cb0993878ae"}, -] - -[package.dependencies] -Sphinx = ">=1.8" +dev = ["bump2version", "sphinxcontrib-httpdomain", "transifex-client"] [[package]] name = "sphinxcontrib-serializinghtml" version = "1.1.5" description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -2823,7 +2529,6 @@ test = ["pytest"] name = "sphinxcontrib-websupport" version = "1.2.4" description = "Sphinx API for Web Apps" -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -2842,7 +2547,6 @@ test = ["Sphinx", "pytest", "sqlalchemy", "whoosh"] name = "tomli" version = "2.0.1" description = "A lil' TOML parser" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2854,7 +2558,6 @@ files = [ name = "toolz" version = "0.12.0" description = "List processing tools and functional utilities" -category = "main" optional = false python-versions = ">=3.5" files = [ @@ -2866,7 +2569,6 @@ files = [ name = "typing-extensions" version = "4.0.1" description = "Backported and Experimental Type Hints for Python 3.6+" -category = "main" optional = false python-versions = ">=3.6" files = [ @@ -2878,7 +2580,6 @@ files = [ name = "typing-validation" version = "1.0.0.post2" description = "A simple library for runtime type-checking." -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -2893,7 +2594,6 @@ dev = ["mypy", "pylint", "pytest", "pytest-cov", "rich"] name = "unflatten" version = "0.1.1" description = "Unflatten dict to dict with nested dict/arrays" -category = "main" optional = false python-versions = "*" files = [ @@ -2903,14 +2603,13 @@ files = [ [[package]] name = "urllib3" -version = "2.0.4" +version = "2.0.5" description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "main" optional = false python-versions = ">=3.7" files = [ - {file = "urllib3-2.0.4-py3-none-any.whl", hash = "sha256:de7df1803967d2c2a98e4b11bb7d6bd9210474c46e8a0401514e3a42a75ebde4"}, - {file = "urllib3-2.0.4.tar.gz", hash = "sha256:8d22f86aae8ef5e410d4f539fde9ce6b2113a001bb4d189e0aed70642d602b11"}, + {file = "urllib3-2.0.5-py3-none-any.whl", hash = "sha256:ef16afa8ba34a1f989db38e1dbbe0c302e4289a47856990d0682e374563ce35e"}, + {file = "urllib3-2.0.5.tar.gz", hash = "sha256:13abf37382ea2ce6fb744d4dad67838eec857c9f4f57009891805e0b5e123594"}, ] [package.extras] @@ -2923,7 +2622,6 @@ zstd = ["zstandard (>=0.18.0)"] name = "ursa-bbs-signatures" version = "1.0.1" description = "" -category = "main" optional = true python-versions = ">=3.6.3" files = [ @@ -2936,7 +2634,6 @@ files = [ name = "varint" version = "1.0.2" description = "Simple python varint implementation" -category = "main" optional = false python-versions = "*" files = [ @@ -2947,7 +2644,6 @@ files = [ name = "virtualenv" version = "20.24.5" description = "Virtual Python Environment builder" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -2968,7 +2664,6 @@ test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess name = "wcwidth" version = "0.2.6" description = "Measures the displayed width of unicode strings in a terminal" -category = "main" optional = false python-versions = "*" files = [ @@ -2976,25 +2671,10 @@ files = [ {file = "wcwidth-0.2.6.tar.gz", hash = "sha256:a5220780a404dbe3353789870978e472cfe477761f06ee55077256e509b156d0"}, ] -[[package]] -name = "web-py" -version = "0.62" -description = "web.py: makes web apps" -category = "main" -optional = false -python-versions = ">=3.5" -files = [ - {file = "web.py-0.62.tar.gz", hash = "sha256:5ce684caa240654cae5950da8b4b7bc178812031e08f990518d072bd44ab525e"}, -] - -[package.dependencies] -cheroot = "*" - [[package]] name = "webargs" version = "5.5.3" description = "Declarative parsing and validation of HTTP request objects, with built-in support for popular web frameworks, including Flask, Django, Bottle, Tornado, Pyramid, webapp2, Falcon, and aiohttp." -category = "main" optional = false python-versions = "*" files = [ @@ -3018,7 +2698,6 @@ tests = ["Django (>=1.11.16)", "Flask (>=0.12.2)", "aiohttp (>=3.0.0)", "bottle name = "wrapt" version = "1.15.0" description = "Module for decorators, wrappers and monkey patching." -category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" files = [ @@ -3103,7 +2782,6 @@ files = [ name = "yarl" version = "1.9.2" description = "Yet another URL library" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -3195,4 +2873,4 @@ indy = ["python3-indy"] [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "72c46c5cf767dcba25d4bfc978b4d71d5f5aa1b1225813975964ae6925b84ec2" +content-hash = "a40e054d44dbd674cae5f6730be7a4fc1d22f4a33284c2eb8195d811326d62f0" diff --git a/pyproject.toml b/pyproject.toml index ecf725499d..4a70aec4bf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,6 @@ python = "^3.9" aiohttp="~3.8.1" aiohttp-apispec="~2.2.1" aiohttp-cors="~0.7.0" -aioredis="~2.0.0" apispec="~3.3.0" async-timeout="~4.0.2" base58="~2.1.0" @@ -30,7 +29,6 @@ jsonpath_ng="1.5.2" Markdown="~3.1.1" markupsafe="2.0.1" marshmallow="~3.20.1" -msgpack="~1.0" multiformats="~0.2.1" nest_asyncio="~1.5.5" packaging="~23.1" @@ -48,9 +46,6 @@ qrcode = {version = ">=6.1,<7.0", extras = ["pil"]} requests="~2.31.0" rlp="1.2.0" unflatten="~0.1" -asyncpg = ">=0.25.0,<0.26.0" -web-py = ">=0.62,<1.0" -pygments = ">=2.10,<3.0" sd_jwt = {git = "https://github.com/openwallet-foundation-labs/sd-jwt-python.git"} # askar From 10e8b54197f8fa3290fb11197862161da626848f Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Mon, 25 Sep 2023 16:26:21 -0400 Subject: [PATCH 71/85] refactor: remove unused mediation terms Signed-off-by: Daniel Bluhm --- .../messaging/models/base_record.py | 8 +-- .../tests/test_mediation_deny_handler.py | 8 +-- .../coordinate_mediation/v1_0/manager.py | 28 +---------- .../v1_0/messages/mediate_deny.py | 24 +-------- .../v1_0/messages/mediate_grant.py | 6 +-- .../v1_0/messages/mediate_request.py | 44 +---------------- .../v1_0/messages/tests/test_mediate_deny.py | 2 +- .../messages/tests/test_mediate_request.py | 2 +- .../v1_0/models/mediation_record.py | 31 ++++++------ .../coordinate_mediation/v1_0/routes.py | 49 +------------------ .../v1_0/tests/test_mediation_manager.py | 2 - .../v1_0/tests/test_routes.py | 27 ++-------- 12 files changed, 37 insertions(+), 194 deletions(-) diff --git a/aries_cloudagent/messaging/models/base_record.py b/aries_cloudagent/messaging/models/base_record.py index 3e94c58a5c..9ebbf74ff6 100644 --- a/aries_cloudagent/messaging/models/base_record.py +++ b/aries_cloudagent/messaging/models/base_record.py @@ -81,11 +81,11 @@ class Meta: def __init__( self, - id: str = None, - state: str = None, + id: Optional[str] = None, + state: Optional[str] = None, *, - created_at: Union[str, datetime] = None, - updated_at: Union[str, datetime] = None, + created_at: Union[str, datetime, None] = None, + updated_at: Union[str, datetime, None] = None, new_with_id: bool = False, ): """Initialize a new BaseRecord.""" diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/handlers/tests/test_mediation_deny_handler.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/handlers/tests/test_mediation_deny_handler.py index 86471117d0..08a8b42c97 100644 --- a/aries_cloudagent/protocols/coordinate_mediation/v1_0/handlers/tests/test_mediation_deny_handler.py +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/handlers/tests/test_mediation_deny_handler.py @@ -11,8 +11,6 @@ from ..mediation_deny_handler import MediationDenyHandler TEST_CONN_ID = "conn-id" -TEST_MEDIATOR_TERMS = ["test", "mediator", "terms"] -TEST_RECIPIENT_TERMS = ["test", "recipient", "terms"] class TestMediationDenyHandler(AsyncTestCase): @@ -22,9 +20,7 @@ async def setUp(self): """Setup test dependencies.""" self.context = RequestContext.test_context() self.session = await self.context.session() - self.context.message = MediationDeny( - mediator_terms=TEST_MEDIATOR_TERMS, recipient_terms=TEST_RECIPIENT_TERMS - ) + self.context.message = MediationDeny() self.context.connection_ready = True self.context.connection_record = ConnRecord(connection_id=TEST_CONN_ID) @@ -50,5 +46,3 @@ async def test_handler(self): ) assert record assert record.state == MediationRecord.STATE_DENIED - assert record.mediator_terms == TEST_MEDIATOR_TERMS - assert record.recipient_terms == TEST_RECIPIENT_TERMS diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/manager.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/manager.py index 8a5a1143ec..0ab45b1434 100644 --- a/aries_cloudagent/protocols/coordinate_mediation/v1_0/manager.py +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/manager.py @@ -139,11 +139,8 @@ async def receive_request( "MediationRecord already exists for connection" ) - # TODO: Determine if terms are acceptable record = MediationRecord( connection_id=connection_id, - mediator_terms=request.mediator_terms, - recipient_terms=request.recipient_terms, ) await record.save(session, reason="New mediation request received") return record @@ -186,19 +183,11 @@ async def grant_request( async def deny_request( self, mediation_id: str, - *, - mediator_terms: Sequence[str] = None, - recipient_terms: Sequence[str] = None, ) -> Tuple[MediationRecord, MediationDeny]: """Deny a mediation request and prepare a deny message. Args: mediation_id: mediation record ID to deny - mediator_terms (Sequence[str]): updated mediator terms to return to - requester. - recipient_terms (Sequence[str]): updated recipient terms to return to - requester. - Returns: MediationDeny: message to return to denied client. @@ -215,9 +204,7 @@ async def deny_request( mediation_record.state = MediationRecord.STATE_DENIED await mediation_record.save(session, reason="Mediation request denied") - deny = MediationDeny( - mediator_terms=mediator_terms, recipient_terms=recipient_terms - ) + deny = MediationDeny() return mediation_record, deny async def _handle_keylist_update_add( @@ -442,15 +429,11 @@ async def clear_default_mediator(self): async def prepare_request( self, connection_id: str, - mediator_terms: Sequence[str] = None, - recipient_terms: Sequence[str] = None, ) -> Tuple[MediationRecord, MediationRequest]: """Prepare a MediationRequest Message, saving a new mediation record. Args: connection_id (str): ID representing mediator - mediator_terms (Sequence[str]): mediator_terms - recipient_terms (Sequence[str]): recipient_terms Returns: MediationRequest: message to send to mediator @@ -459,15 +442,11 @@ async def prepare_request( record = MediationRecord( role=MediationRecord.ROLE_CLIENT, connection_id=connection_id, - mediator_terms=mediator_terms, - recipient_terms=recipient_terms, ) async with self._profile.session() as session: await record.save(session, reason="Creating new mediation request.") - request = MediationRequest( - mediator_terms=mediator_terms, recipient_terms=recipient_terms - ) + request = MediationRequest() return record, request async def request_granted(self, record: MediationRecord, grant: MediationGrant): @@ -495,9 +474,6 @@ async def request_denied(self, record: MediationRecord, deny: MediationDeny): """ record.state = MediationRecord.STATE_DENIED - # TODO Record terms elsewhere? - record.mediator_terms = deny.mediator_terms - record.recipient_terms = deny.recipient_terms async with self._profile.session() as session: await record.save(session, reason="Mediation request denied.") diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/messages/mediate_deny.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/messages/mediate_deny.py index bd6496ab63..b37f58dba7 100644 --- a/aries_cloudagent/protocols/coordinate_mediation/v1_0/messages/mediate_deny.py +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/messages/mediate_deny.py @@ -1,8 +1,5 @@ """mediate-deny message used to notify mediation client of a denied mediation request.""" -from typing import Sequence - -from marshmallow import fields from .....messaging.agent_message import AgentMessage, AgentMessageSchema from ..message_types import MEDIATE_DENY, PROTOCOL_PACKAGE @@ -24,20 +21,10 @@ class Meta: def __init__( self, - *, - mediator_terms: Sequence[str] = None, - recipient_terms: Sequence[str] = None, **kwargs, ): - """Initialize mediation deny object. - - Args: - mediator_terms: Terms that were agreed by the recipient - recipient_terms: Terms that recipient wants to mediator to agree to - """ + """Initialize mediation deny object.""" super(MediationDeny, self).__init__(**kwargs) - self.mediator_terms = list(mediator_terms) if mediator_terms else [] - self.recipient_terms = list(recipient_terms) if recipient_terms else [] class MediationDenySchema(AgentMessageSchema): @@ -47,12 +34,3 @@ class Meta: """Mediation deny schema metadata.""" model_class = MediationDeny - - mediator_terms = fields.List( - fields.Str(metadata={"description": "Terms for mediator to agree"}), - required=False, - ) - recipient_terms = fields.List( - fields.Str(metadata={"description": "Terms for recipient to agree"}), - required=False, - ) diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/messages/mediate_grant.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/messages/mediate_grant.py index 587f61730a..d2595ede53 100644 --- a/aries_cloudagent/protocols/coordinate_mediation/v1_0/messages/mediate_grant.py +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/messages/mediate_grant.py @@ -3,7 +3,7 @@ Used to notify mediation client of a granted mediation request. """ -from typing import Sequence +from typing import Optional, Sequence from marshmallow import fields @@ -29,8 +29,8 @@ class Meta: def __init__( self, *, - endpoint: str = None, - routing_keys: Sequence[str] = None, + endpoint: Optional[str] = None, + routing_keys: Optional[Sequence[str]] = None, **kwargs, ): """Initialize mediation grant object. diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/messages/mediate_request.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/messages/mediate_request.py index 84417ab830..2d47dc45eb 100644 --- a/aries_cloudagent/protocols/coordinate_mediation/v1_0/messages/mediate_request.py +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/messages/mediate_request.py @@ -1,8 +1,5 @@ """mediate-request message used to request mediation from a mediator.""" -from typing import Sequence - -from marshmallow import fields from .....messaging.agent_message import AgentMessage, AgentMessageSchema from ..message_types import MEDIATE_REQUEST, PROTOCOL_PACKAGE @@ -22,22 +19,9 @@ class Meta: message_type = MEDIATE_REQUEST schema_class = "MediationRequestSchema" - def __init__( - self, - *, - mediator_terms: Sequence[str] = None, - recipient_terms: Sequence[str] = None, - **kwargs, - ): - """Initialize mediation request object. - - Args: - mediator_terms: Mediator's terms for granting mediation. - recipient_terms: Recipient's proposed mediation terms. - """ + def __init__(self, **kwargs): + """Initialize mediation request object.""" super(MediationRequest, self).__init__(**kwargs) - self.mediator_terms = list(mediator_terms) if mediator_terms else [] - self.recipient_terms = list(recipient_terms) if recipient_terms else [] class MediationRequestSchema(AgentMessageSchema): @@ -47,27 +31,3 @@ class Meta: """Mediation request schema metadata.""" model_class = MediationRequest - - mediator_terms = fields.List( - fields.Str( - metadata={ - "description": ( - "Indicate terms that the mediator requires the recipient to" - " agree to" - ) - } - ), - required=False, - metadata={"description": "List of mediator rules for recipient"}, - ) - recipient_terms = fields.List( - fields.Str( - metadata={ - "description": ( - "Indicate terms that the recipient requires the mediator to" - " agree to" - ) - } - ), - required=False, - ) diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/messages/tests/test_mediate_deny.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/messages/tests/test_mediate_deny.py index 5b82e5430e..bff806b0ed 100644 --- a/aries_cloudagent/protocols/coordinate_mediation/v1_0/messages/tests/test_mediate_deny.py +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/messages/tests/test_mediate_deny.py @@ -12,4 +12,4 @@ class TestMediateDeny(MessageTest, TestCase): TYPE = MEDIATE_DENY CLASS = MediationDeny SCHEMA = MediationDenySchema - VALUES = {"mediator_terms": ["test", "terms"], "recipient_terms": ["test", "terms"]} + VALUES = {} diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/messages/tests/test_mediate_request.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/messages/tests/test_mediate_request.py index fa99cf71b9..bb9ee0713b 100644 --- a/aries_cloudagent/protocols/coordinate_mediation/v1_0/messages/tests/test_mediate_request.py +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/messages/tests/test_mediate_request.py @@ -12,4 +12,4 @@ class TestMediateRequest(MessageTest, TestCase): TYPE = MEDIATE_REQUEST CLASS = MediationRequest SCHEMA = MediationRequestSchema - VALUES = {"mediator_terms": ["test", "terms"], "recipient_terms": ["test", "terms"]} + VALUES = {} diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/models/mediation_record.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/models/mediation_record.py index 6e3af3c726..9273801915 100644 --- a/aries_cloudagent/protocols/coordinate_mediation/v1_0/models/mediation_record.py +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/models/mediation_record.py @@ -1,6 +1,6 @@ """Store state for Mediation requests.""" -from typing import Sequence +from typing import Optional, Sequence from marshmallow import EXCLUDE, fields @@ -33,14 +33,15 @@ class Meta: def __init__( self, *, - mediation_id: str = None, - state: str = None, - role: str = None, - connection_id: str = None, - mediator_terms: Sequence[str] = None, - recipient_terms: Sequence[str] = None, - routing_keys: Sequence[str] = None, - endpoint: str = None, + mediation_id: Optional[str] = None, + state: Optional[str] = None, + role: Optional[str] = None, + connection_id: Optional[str] = None, + routing_keys: Optional[Sequence[str]] = None, + endpoint: Optional[str] = None, + # Included for record backwards compat + mediator_terms: Optional[Sequence[str]] = None, + recipient_terms: Optional[Sequence[str]] = None, **kwargs, ): """__init__. @@ -50,8 +51,6 @@ def __init__( state (str): state, defaults to 'request_received' role (str): role in mediation, defaults to 'server' connection_id (str): ID of connection requesting or managing mediation - mediator_terms (Sequence[str]): mediator_terms - recipient_terms (Sequence[str]): recipient_terms routing_keys (Sequence[str]): keys in mediator control used to receive incoming messages endpoint (str): mediators endpoint @@ -61,8 +60,6 @@ def __init__( super().__init__(mediation_id, state or self.STATE_REQUEST, **kwargs) self.role = role if role else self.ROLE_SERVER self.connection_id = connection_id - self.mediator_terms = list(mediator_terms) if mediator_terms else [] - self.recipient_terms = list(recipient_terms) if recipient_terms else [] self.routing_keys = list(routing_keys) if routing_keys else [] self.endpoint = endpoint @@ -79,6 +76,8 @@ def __eq__(self, other: "MediationRecord"): @property def mediation_id(self) -> str: """Get Mediation ID.""" + if not self._id: + raise ValueError("Record not yet stored") return self._id @property @@ -170,10 +169,12 @@ class Meta: mediation_id = fields.Str(required=False) role = fields.Str(required=True) connection_id = fields.Str(required=True) - mediator_terms = fields.List(fields.Str(), required=False) - recipient_terms = fields.List(fields.Str(), required=False) routing_keys = fields.List( fields.Str(validate=DID_KEY_VALIDATE, metadata={"example": DID_KEY_EXAMPLE}), required=False, ) endpoint = fields.Str(required=False) + + # Included for backwards compat with old records + mediator_terms = fields.List(fields.Str(), required=False) + recipient_terms = fields.List(fields.Str(), required=False) diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/routes.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/routes.py index 82e04faabc..52b87058fd 100644 --- a/aries_cloudagent/protocols/coordinate_mediation/v1_0/routes.py +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/routes.py @@ -62,32 +62,6 @@ ) -MEDIATOR_TERMS_SCHEMA = fields.List( - fields.Str( - metadata={ - "description": ( - "Indicate terms to which the mediator requires the recipient to agree" - ) - } - ), - required=False, - metadata={"description": "List of mediator rules for recipient"}, -) - - -RECIPIENT_TERMS_SCHEMA = fields.List( - fields.Str( - metadata={ - "description": ( - "Indicate terms to which the recipient requires the mediator to agree" - ) - } - ), - required=False, - metadata={"description": "List of recipient rules for mediation"}, -) - - class MediationListSchema(OpenAPISchema): """Result schema for mediation list query.""" @@ -101,24 +75,16 @@ class MediationListQueryStringSchema(OpenAPISchema): """Parameters and validators for mediation record list request query string.""" conn_id = CONNECTION_ID_SCHEMA - mediator_terms = MEDIATOR_TERMS_SCHEMA - recipient_terms = RECIPIENT_TERMS_SCHEMA state = MEDIATION_STATE_SCHEMA class MediationCreateRequestSchema(OpenAPISchema): """Parameters and validators for create Mediation request query string.""" - mediator_terms = MEDIATOR_TERMS_SCHEMA - recipient_terms = RECIPIENT_TERMS_SCHEMA - class AdminMediationDenySchema(OpenAPISchema): """Parameters and validators for Mediation deny admin request query string.""" - mediator_terms = MEDIATOR_TERMS_SCHEMA - recipient_terms = RECIPIENT_TERMS_SCHEMA - class MediationIdMatchInfoSchema(OpenAPISchema): """Path parameters and validators for request taking mediation request id.""" @@ -281,10 +247,6 @@ async def request_mediation(request: web.BaseRequest): conn_id = request.match_info["conn_id"] - body = await request.json() - mediator_terms = body.get("mediator_terms") - recipient_terms = body.get("recipient_terms") - try: async with profile.session() as session: connection_record = await ConnRecord.retrieve_by_id(session, conn_id) @@ -300,11 +262,7 @@ async def request_mediation(request: web.BaseRequest): mediation_record, mediation_request = await MediationManager( profile - ).prepare_request( - connection_id=conn_id, - mediator_terms=mediator_terms, - recipient_terms=recipient_terms, - ) + ).prepare_request(connection_id=conn_id) result = mediation_record.serialize() except StorageNotFoundError as err: @@ -348,15 +306,10 @@ async def mediation_request_deny(request: web.BaseRequest): context: AdminRequestContext = request["context"] outbound_handler = request["outbound_message_router"] mediation_id = request.match_info.get("mediation_id") - body = await request.json() - mediator_terms = body.get("mediator_terms") - recipient_terms = body.get("recipient_terms") try: mediation_manager = MediationManager(context.profile) record, deny_request = await mediation_manager.deny_request( mediation_id=mediation_id, - mediator_terms=mediator_terms, - recipient_terms=recipient_terms, ) result = record.serialize() except StorageNotFoundError as err: diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_mediation_manager.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_mediation_manager.py index 5ccab85c5f..171e686343 100644 --- a/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_mediation_manager.py +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_mediation_manager.py @@ -130,8 +130,6 @@ async def test_deny_request(self, manager): record = await manager.receive_request(TEST_CONN_ID, request) assert record.connection_id == TEST_CONN_ID record, deny = await manager.deny_request(record.mediation_id) - assert deny.mediator_terms == [] - assert deny.recipient_terms == [] async def test_update_keylist_delete(self, session, manager, record): """test_update_keylist_delete.""" diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_routes.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_routes.py index ababf0ea16..d4b3a83c99 100644 --- a/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_routes.py +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_routes.py @@ -33,8 +33,6 @@ def setUp(self): "state": "granted", "role": "server", "connection_id": "c3dd00cf-f6a2-4ddf-93d8-49ae74bdacef", - "mediator_terms": [], - "recipient_terms": [], "routing_keys": ["EwUKjVLboiLSuoWSEtDvrgrd41EUxG5bLecQrkHB63Up"], "endpoint": "http://192.168.1.13:3005", "created_at": "1234567890", @@ -212,10 +210,7 @@ async def test_delete_mediation_request_x_storage_error(self): await test_module.delete_mediation_request(self.request) async def test_request_mediation(self): - body = { - "mediator_terms": ["meaningless string because terms are not used"], - "recipient_terms": ["meaningless string because terms are not a 'thing'"], - } + body = {} self.request.json.return_value = body with async_mock.patch.object( test_module, "MediationManager", autospec=True @@ -243,10 +238,7 @@ async def test_request_mediation(self): self.outbound_message_router.assert_called() async def test_request_mediation_x_conn_not_ready(self): - body = { - "mediator_terms": ["meaningless string because terms are not used"], - "recipient_terms": ["meaningless string because terms are not a 'thing'"], - } + body = {} self.request.json.return_value = body with async_mock.patch.object( test_module.ConnRecord, @@ -259,10 +251,7 @@ async def test_request_mediation_x_conn_not_ready(self): assert "request connection is not ready" in exc.msg async def test_request_mediation_x_already_exists(self): - body = { - "mediator_terms": ["meaningless string because terms are not used"], - "recipient_terms": ["meaningless string because terms are not a 'thing'"], - } + body = {} self.request.json.return_value = body with async_mock.patch.object( test_module.ConnRecord, "retrieve_by_id", async_mock.CoroutineMock() @@ -277,10 +266,7 @@ async def test_request_mediation_x_already_exists(self): assert "already exists for connection" in exc.msg async def test_request_mediation_x_conn_not_found(self): - body = { - "mediator_terms": ["meaningless string because terms are not used"], - "recipient_terms": ["meaningless string because terms are not a 'thing'"], - } + body = {} self.request.json.return_value = body with async_mock.patch.object( test_module.ConnRecord, @@ -292,10 +278,7 @@ async def test_request_mediation_x_conn_not_found(self): await test_module.request_mediation(self.request) async def test_request_mediation_x_storage_error(self): - body = { - "mediator_terms": ["meaningless string because terms are not used"], - "recipient_terms": ["meaningless string because terms are not a 'thing'"], - } + body = {} self.request.json.return_value = body with async_mock.patch.object( test_module.ConnRecord, From d18fb6951d952938dcae96db1e182d4f12a1d34e Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Mon, 25 Sep 2023 16:42:56 -0400 Subject: [PATCH 72/85] test: mediator_terms record backwards compat Signed-off-by: Daniel Bluhm --- .../v1_0/models/mediation_record.py | 2 - .../v1_0/models/tests/__init__.py | 1 + .../models/tests/test_mediation_record.py | 62 +++++++++++++++++++ .../v1_0/tests/test_mediation_manager.py | 14 ----- 4 files changed, 63 insertions(+), 16 deletions(-) create mode 100644 aries_cloudagent/protocols/coordinate_mediation/v1_0/models/tests/__init__.py create mode 100644 aries_cloudagent/protocols/coordinate_mediation/v1_0/models/tests/test_mediation_record.py diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/models/mediation_record.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/models/mediation_record.py index 9273801915..f616864a47 100644 --- a/aries_cloudagent/protocols/coordinate_mediation/v1_0/models/mediation_record.py +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/models/mediation_record.py @@ -108,8 +108,6 @@ def record_value(self) -> dict: return { prop: getattr(self, prop) for prop in ( - "mediator_terms", - "recipient_terms", "routing_keys", "endpoint", ) diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/models/tests/__init__.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/models/tests/__init__.py new file mode 100644 index 0000000000..7c3c4e3c4d --- /dev/null +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/models/tests/__init__.py @@ -0,0 +1 @@ +"""Tests for models.""" diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/models/tests/test_mediation_record.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/models/tests/test_mediation_record.py new file mode 100644 index 0000000000..546f1f2128 --- /dev/null +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/models/tests/test_mediation_record.py @@ -0,0 +1,62 @@ +"""Test mediation record.""" +import json + +import pytest + +from ......core.in_memory import InMemoryProfile +from ......core.profile import ProfileSession +from ......storage.base import BaseStorage +from ......storage.record import StorageRecord +from ..mediation_record import MediationRecord + + +@pytest.fixture() +async def session(): + profile = InMemoryProfile.test_profile() + async with profile.session() as session: + yield session + + +@pytest.mark.asyncio +async def test_backwards_compat_terms(session: ProfileSession): + """Make sure old records can be loaded still.""" + + old_record = StorageRecord( + MediationRecord.RECORD_TYPE, + id="test_mediation_id", + value=json.dumps( + { + "state": "granted", + "mediator_terms": ["dummy_terms"], + "recipient_terms": ["dummy_terms"], + } + ), + ) + storage = session.inject(BaseStorage) + await storage.add_record(old_record) + + record = await MediationRecord.retrieve_by_id(session, old_record.id) + assert isinstance(record, MediationRecord) + assert record.mediation_id == "test_mediation_id" + assert record.state == "granted" + + +@pytest.mark.asyncio +async def test_mediation_record_eq(): + record_0 = MediationRecord(mediation_id="test_medation_id_0", endpoint="zero") + record_1 = MediationRecord(mediation_id="test_medation_id_1", endpoint="one") + assert record_0 != record_1 + + with pytest.raises(ValueError): + record_0.state = "bad state" + + +@pytest.mark.asyncio +async def test_mediation_record_duplicate_means_exists(session: ProfileSession): + await MediationRecord(connection_id="test_connection_id", endpoint="abc").save( + session + ) + await MediationRecord(connection_id="test_connection_id", endpoint="def").save( + session + ) + assert await MediationRecord.exists_for_connection_id(session, "test_connection_id") diff --git a/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_mediation_manager.py b/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_mediation_manager.py index 171e686343..188f600c99 100644 --- a/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_mediation_manager.py +++ b/aries_cloudagent/protocols/coordinate_mediation/v1_0/tests/test_mediation_manager.py @@ -214,20 +214,6 @@ async def test_get_keylist_no_granted_record(self, manager): with pytest.raises(MediationNotGrantedError): await manager.get_keylist(record) - async def test_mediation_record_eq(self): - record_0 = MediationRecord(endpoint="zero") - record_1 = MediationRecord(endpoint="one") - assert record_0 != record_1 - assert record_0 != ValueError("not a mediation record") - - with pytest.raises(ValueError): - record_0.state = "bad state" - - async def test_mediation_record_duplicate_means_exists(self, session): - await MediationRecord(connection_id=TEST_CONN_ID, endpoint="abc").save(session) - await MediationRecord(connection_id=TEST_CONN_ID, endpoint="def").save(session) - assert await MediationRecord.exists_for_connection_id(session, TEST_CONN_ID) - async def test_create_keylist_query_response(self, session, manager, record): """test_create_keylist_query_response.""" await RouteRecord(connection_id=TEST_CONN_ID, recipient_key=TEST_VERKEY).save( From 3b51670e4e0e66b654ae7ebb134b693e2b96ccfe Mon Sep 17 00:00:00 2001 From: Daniel Bluhm Date: Mon, 25 Sep 2023 17:36:46 -0400 Subject: [PATCH 73/85] fix: mediation ids on test records Signed-off-by: Daniel Bluhm --- .../protocols/connections/v1_0/tests/test_manager.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/aries_cloudagent/protocols/connections/v1_0/tests/test_manager.py b/aries_cloudagent/protocols/connections/v1_0/tests/test_manager.py index fbd3a8f6fe..b881d2fd34 100644 --- a/aries_cloudagent/protocols/connections/v1_0/tests/test_manager.py +++ b/aries_cloudagent/protocols/connections/v1_0/tests/test_manager.py @@ -463,6 +463,7 @@ async def test_create_request_multitenant(self): async def test_create_request_mediation_id(self): mediation_record = MediationRecord( + mediation_id="test_medation_id", role=MediationRecord.ROLE_CLIENT, state=MediationRecord.STATE_GRANTED, connection_id=self.test_mediator_conn_id, @@ -866,6 +867,7 @@ async def test_create_response_multitenant(self): ) mediation_record = MediationRecord( + mediation_id="test_medation_id", role=MediationRecord.ROLE_CLIENT, state=MediationRecord.STATE_GRANTED, connection_id=self.test_mediator_conn_id, @@ -936,6 +938,7 @@ async def test_create_response_bad_state(self): async def test_create_response_mediation(self): mediation_record = MediationRecord( + mediation_id="test_medation_id", role=MediationRecord.ROLE_CLIENT, state=MediationRecord.STATE_GRANTED, connection_id=self.test_mediator_conn_id, From 9b16660508c3b1e9c70af46e38afbe51f8fcc6d1 Mon Sep 17 00:00:00 2001 From: Shaanjot Gill Date: Sat, 9 Sep 2023 01:50:18 +0530 Subject: [PATCH 74/85] parent 49e71c8aae319c7d805160696fecb1bcb85f139d author Shaanjot Gill 1694204418 +0530 committer Shaanjot Gill 1695747717 +0530 parent 49e71c8aae319c7d805160696fecb1bcb85f139d author Shaanjot Gill 1694204418 +0530 committer Shaanjot Gill 1695747613 +0530 parent 49e71c8aae319c7d805160696fecb1bcb85f139d author Shaanjot Gill 1694204418 +0530 committer Shaanjot Gill 1695747425 +0530 parent 49e71c8aae319c7d805160696fecb1bcb85f139d author Shaanjot Gill 1694204418 +0530 committer Shaanjot Gill 1695746776 +0530 tagged upgrade impl, fixed fix_ledger_entry function and added tests Signed-off-by: Shaanjot Gill --- UpgradingACA-Py.md | 19 ++ .../default_version_upgrade_config.yml | 4 +- .../commands/tests/test_upgrade.py | 42 ++- aries_cloudagent/commands/upgrade.py | 323 ++++++++++++------ aries_cloudagent/config/argparse.py | 11 + .../models/issuer_rev_reg_record.py | 89 +++-- .../tests/test_issuer_rev_reg_record.py | 191 ++++++++++- aries_cloudagent/revocation/recover.py | 8 +- 8 files changed, 543 insertions(+), 144 deletions(-) diff --git a/UpgradingACA-Py.md b/UpgradingACA-Py.md index 0ad8708564..a829bc4f7f 100644 --- a/UpgradingACA-Py.md +++ b/UpgradingACA-Py.md @@ -59,6 +59,25 @@ is high (such as a "resave connections" upgrade to a deployment with many, many connections), you may want to do a test upgrade offline first, to see if there is likely to be a service disruption during the upgrade. Plan accordingly! +## Tagged upgrades +Upgrades are defined in the [Upgrade Definition YML file], in addition to specifying upgrade actions by version they can also be specified by named tags. Unlike version based upgrades where all applicable version based actions will be performed based upon sorted order of versions, with named tags only actions corresponding to provided tags will be performed. Note: `--force-upgrade` is required when running name tags based upgrade [i.e. provding `--named-tag`] + +Tags are specfied in YML file as below: +``` +fix_issue_rev_reg: + fix_issue_rev_reg_records: true +``` + +Example +``` + ./scripts/run_docker upgrade --force-upgrade --named-tag fix_issue_rev_reg + +In case, running multiple tags [say test1 & test2]: + ./scripts/run_docker upgrade --force-upgrade --named-tag test1 --named-tag test2 +``` + + + ## Exceptions There are a couple of upgrade exception conditions to consider, as outlined diff --git a/aries_cloudagent/commands/default_version_upgrade_config.yml b/aries_cloudagent/commands/default_version_upgrade_config.yml index 8c04b9e48f..25cc32538b 100644 --- a/aries_cloudagent/commands/default_version_upgrade_config.yml +++ b/aries_cloudagent/commands/default_version_upgrade_config.yml @@ -13,4 +13,6 @@ v0.7.1: v0.7.0: update_existing_records: false v0.6.0: - update_existing_records: false \ No newline at end of file + update_existing_records: false +fix_issue_rev_reg: + fix_issue_rev_reg_records: true diff --git a/aries_cloudagent/commands/tests/test_upgrade.py b/aries_cloudagent/commands/tests/test_upgrade.py index 3cbdda9831..d6c5c66e08 100644 --- a/aries_cloudagent/commands/tests/test_upgrade.py +++ b/aries_cloudagent/commands/tests/test_upgrade.py @@ -121,6 +121,44 @@ async def test_upgrade_callable(self): } ) + async def test_upgrade_callable_named_tag(self): + version_storage_record = await self.storage.find_record( + type_filter="acapy_version", tag_query={} + ) + await self.storage.delete_record(version_storage_record) + with async_mock.patch.object( + test_module, + "wallet_config", + async_mock.CoroutineMock( + return_value=( + self.profile, + async_mock.CoroutineMock(did="public DID", verkey="verkey"), + ) + ), + ), async_mock.patch.object( + test_module.yaml, + "safe_load", + async_mock.MagicMock( + return_value={ + "v0.7.2": { + "resave_records": { + "base_record_path": [ + "aries_cloudagent.connections.models.conn_record.ConnRecord" + ] + }, + "update_existing_records": True, + }, + "fix_issue_rev_reg": {"fix_issue_rev_reg_records": True}, + } + ), + ): + await test_module.upgrade( + settings={ + "upgrade.named_tags": ["fix_issue_rev_reg"], + "upgrade.force_upgrade": True, + } + ) + async def test_upgrade_x_same_version(self): version_storage_record = await self.storage.find_record( type_filter="acapy_version", tag_query={} @@ -169,7 +207,9 @@ async def test_upgrade_missing_from_version(self): "upgrade.config_path": "./aries_cloudagent/commands/default_version_upgrade_config.yml", } ) - assert "No upgrade from version found in wallet or" in str(ctx.exception) + assert "Error during upgrade: No upgrade from version or tags found" in str( + ctx.exception + ) async def test_upgrade_x_callable_not_set(self): version_storage_record = await self.storage.find_record( diff --git a/aries_cloudagent/commands/upgrade.py b/aries_cloudagent/commands/upgrade.py index 8706c61bcf..bf2d0dc95a 100644 --- a/aries_cloudagent/commands/upgrade.py +++ b/aries_cloudagent/commands/upgrade.py @@ -3,6 +3,7 @@ import asyncio import logging import os +import json import yaml from configargparse import ArgumentParser @@ -19,16 +20,18 @@ Tuple, ) -from ..core.profile import Profile +from ..core.profile import Profile, ProfileSession from ..config import argparse as arg from ..config.default_context import DefaultContextBuilder from ..config.base import BaseError, BaseSettings from ..config.util import common_config from ..config.wallet import wallet_config -from ..messaging.models.base_record import BaseRecord +from ..messaging.models.base import BaseModelError +from ..messaging.models.base_record import BaseRecord, RecordType from ..storage.base import BaseStorage from ..storage.error import StorageNotFoundError from ..storage.record import StorageRecord +from ..revocation.models.issuer_rev_reg_record import IssuerRevRegRecord from ..utils.classloader import ClassLoader, ClassNotFoundError from ..version import __version__, RECORD_TYPE_ACAPY_VERSION @@ -78,10 +81,10 @@ def setup_version_upgrade_config(self, path: str): """Set ups config dict from the provided YML file.""" with open(path, "r") as stream: config_dict = yaml.safe_load(stream) - version_config_dict = {} - for version, provided_config in config_dict.items(): + tagged_config_dict = {} + for config_id, provided_config in config_dict.items(): recs_list = [] - version_config_dict[version] = {} + tagged_config_dict[config_id] = {} if "resave_records" in provided_config: if provided_config.get("resave_records").get("base_record_path"): recs_list = recs_list + provided_config.get( @@ -93,14 +96,14 @@ def setup_version_upgrade_config(self, path: str): recs_list = recs_list + provided_config.get( "resave_records" ).get("base_exch_record_path") - version_config_dict[version]["resave_records"] = recs_list + tagged_config_dict[config_id]["resave_records"] = recs_list config_key_set = set(provided_config.keys()) try: config_key_set.remove("resave_records") except KeyError: pass if "explicit_upgrade" in provided_config: - version_config_dict[version][ + tagged_config_dict[config_id][ "explicit_upgrade" ] = provided_config.get("explicit_upgrade") try: @@ -108,12 +111,12 @@ def setup_version_upgrade_config(self, path: str): except KeyError: pass for executable in config_key_set: - version_config_dict[version][executable] = ( + tagged_config_dict[config_id][executable] = ( provided_config.get(executable) or False ) - if version_config_dict == {}: + if tagged_config_dict == {}: raise UpgradeError(f"No version configs found in {path}") - self.upgrade_configs = version_config_dict + self.upgrade_configs = tagged_config_dict def get_callable(self, executable: str) -> Optional[Callable]: """Return callable function for executable name.""" @@ -159,9 +162,12 @@ def get_upgrade_version_list( if not sorted_version_list: version_upgrade_config_inst = VersionUpgradeConfig(config_path) upgrade_configs = version_upgrade_config_inst.upgrade_configs - versions_found_in_config = upgrade_configs.keys() + tags_found_in_config = upgrade_configs.keys() + version_found_in_config, _ = _get_version_and_name_tags( + list(tags_found_in_config) + ) sorted_version_list = sorted( - versions_found_in_config, key=lambda x: package_version.parse(x) + version_found_in_config, key=lambda x: package_version.parse(x) ) version_list = [] @@ -193,6 +199,46 @@ async def add_version_record(profile: Profile, version: str): LOGGER.info(f"{RECORD_TYPE_ACAPY_VERSION} storage record set to {version}") +def _get_version_and_name_tags(tags_found_in_config: List) -> Tuple[List, List]: + """Get version and named tag key lists from config.""" + version_found_in_config = [] + named_tag_found_in_config = [] + for tag in tags_found_in_config: + try: + package_version.parse(tag) + version_found_in_config.append(tag) + except package_version.InvalidVersion: + named_tag_found_in_config.append(tag) + return version_found_in_config, named_tag_found_in_config + + +def _perform_upgrade( + upgrade_config: dict, + resave_record_path_sets: set, + executables_call_set: set, + tag: str, +) -> Tuple[set, set]: + """Update and return resave record path and executables call sets.""" + LOGGER.info(f"Running upgrade process for {tag}") + # Step 1 re-saving all BaseRecord and BaseExchangeRecord + if "resave_records" in upgrade_config: + resave_record_paths = upgrade_config.get("resave_records") + for record_path in resave_record_paths: + resave_record_path_sets.add(record_path) + + # Step 2 Update existing records, if required + config_key_set = set(upgrade_config.keys()) + try: + config_key_set.remove("resave_records") + except KeyError: + pass + for callable_name in list(config_key_set): + if upgrade_config.get(callable_name) is False: + continue + executables_call_set.add(callable_name) + return resave_record_path_sets, executables_call_set + + async def upgrade( settings: Optional[Union[Mapping[str, Any], BaseSettings]] = None, profile: Optional[Profile] = None, @@ -211,11 +257,18 @@ async def upgrade( version_upgrade_config_inst = VersionUpgradeConfig( settings.get("upgrade.config_path") ) + upgrade_from_tags = None + force_upgrade_flag = settings.get("upgrade.force_upgrade") or False + if force_upgrade_flag: + upgrade_from_tags = settings.get("upgrade.named_tags") upgrade_configs = version_upgrade_config_inst.upgrade_configs upgrade_to_version = f"v{__version__}" - versions_found_in_config = upgrade_configs.keys() + tags_found_in_config = upgrade_configs.keys() + version_found_in_config, named_tag_found_in_config = _get_version_and_name_tags( + list(tags_found_in_config) + ) sorted_versions_found_in_config = sorted( - versions_found_in_config, key=lambda x: package_version.parse(x) + version_found_in_config, key=lambda x: package_version.parse(x) ) upgrade_from_version_storage = None upgrade_from_version_config = None @@ -240,7 +293,6 @@ async def upgrade( ) ) - force_upgrade_flag = settings.get("upgrade.force_upgrade") or False if upgrade_from_version_storage and upgrade_from_version_config: if ( package_version.parse(upgrade_from_version_storage) @@ -261,103 +313,100 @@ async def upgrade( and not upgrade_from_version_config ): upgrade_from_version = upgrade_from_version_storage - if not upgrade_from_version: + if not upgrade_from_version and not upgrade_from_tags: raise UpgradeError( - "No upgrade from version found in wallet or settings [--from-version]" + "No upgrade from version or tags found in wallet" + " or settings [--from-version or --named-tag]" ) - upgrade_version_in_config = get_upgrade_version_list( - sorted_version_list=sorted_versions_found_in_config, - from_version=upgrade_from_version, - ) - # Perform explicit upgrade check if the function was called during startup - if profile: - ( - explicit_flag, - to_skip_explicit_versions, - explicit_upg_ver, - ) = explicit_upgrade_required_check( - to_apply_version_list=upgrade_version_in_config, - upgrade_config=upgrade_configs, - ) - if explicit_flag: - raise UpgradeError( - "Explicit upgrade flag with critical value found " - f"for {explicit_upg_ver} config. Please use ACA-Py " - "upgrade command to complete the process and proceed." - ) - if len(to_skip_explicit_versions) >= 1: - LOGGER.warning( - "Explicit upgrade flag with warning value found " - f"for {str(to_skip_explicit_versions)} versions. " - "Proceeding with ACA-Py startup. You can apply " - "the explicit upgrades using the ACA-Py upgrade " - "command later." - ) - return + resave_record_path_sets = set() + executables_call_set = set() to_update_flag = False - if upgrade_from_version == upgrade_to_version: - LOGGER.info( + if upgrade_from_version: + upgrade_version_in_config = get_upgrade_version_list( + sorted_version_list=sorted_versions_found_in_config, + from_version=upgrade_from_version, + ) + # Perform explicit upgrade check if the function was called during startup + if profile: ( - f"Version {upgrade_from_version} to upgrade from and " - f"current version to upgrade to {upgrade_to_version} " - "are same. You can apply upgrade from a lower " - "version by running the upgrade command with " - f"--from-version [< {upgrade_to_version}] and " - "--force-upgrade" + explicit_flag, + to_skip_explicit_versions, + explicit_upg_ver, + ) = explicit_upgrade_required_check( + to_apply_version_list=upgrade_version_in_config, + upgrade_config=upgrade_configs, ) - ) - else: - resave_record_path_sets = set() - executables_call_set = set() - for config_from_version in upgrade_version_in_config: - LOGGER.info(f"Running upgrade process for {config_from_version}") - upgrade_config = upgrade_configs.get(config_from_version) - # Step 1 re-saving all BaseRecord and BaseExchangeRecord - if "resave_records" in upgrade_config: - resave_record_paths = upgrade_config.get("resave_records") - for record_path in resave_record_paths: - resave_record_path_sets.add(record_path) - - # Step 2 Update existing records, if required - config_key_set = set(upgrade_config.keys()) - try: - config_key_set.remove("resave_records") - except KeyError: - pass - for callable_name in list(config_key_set): - if upgrade_config.get(callable_name) is False: - continue - executables_call_set.add(callable_name) - - if len(resave_record_path_sets) >= 1 or len(executables_call_set) >= 1: - to_update_flag = True - for record_path in resave_record_path_sets: - try: - rec_type = ClassLoader.load_class(record_path) - except ClassNotFoundError as err: - raise UpgradeError(f"Unknown Record type {record_path}") from err - if not issubclass(rec_type, BaseRecord): + if explicit_flag: raise UpgradeError( - f"Only BaseRecord can be resaved, found: {str(rec_type)}" + "Explicit upgrade flag with critical value found " + f"for {explicit_upg_ver} config. Please use ACA-Py " + "upgrade command to complete the process and proceed." ) - async with root_profile.session() as session: - all_records = await rec_type.query(session) - for record in all_records: - await record.save( - session, - reason="re-saving record during the upgrade process", - ) - if len(all_records) == 0: - LOGGER.info(f"No records of {str(rec_type)} found") - else: - LOGGER.info( - f"All recs of {str(rec_type)} successfully re-saved" - ) - for callable_name in executables_call_set: - _callable = version_upgrade_config_inst.get_callable(callable_name) - if not _callable: - raise UpgradeError(f"No function specified for {callable_name}") - await _callable(root_profile) + if len(to_skip_explicit_versions) >= 1: + LOGGER.warning( + "Explicit upgrade flag with warning value found " + f"for {str(to_skip_explicit_versions)} versions. " + "Proceeding with ACA-Py startup. You can apply " + "the explicit upgrades using the ACA-Py upgrade " + "command later." + ) + return + if upgrade_from_version == upgrade_to_version: + LOGGER.info( + ( + f"Version {upgrade_from_version} to upgrade from and " + f"current version to upgrade to {upgrade_to_version} " + "are same. You can apply upgrade from a lower " + "version by running the upgrade command with " + f"--from-version [< {upgrade_to_version}] and " + "--force-upgrade" + ) + ) + else: + for config_from_version in upgrade_version_in_config: + resave_record_path_sets, executables_call_set = _perform_upgrade( + upgrade_config=upgrade_configs.get(config_from_version), + resave_record_path_sets=resave_record_path_sets, + executables_call_set=executables_call_set, + tag=config_from_version, + ) + if upgrade_from_tags and len(upgrade_from_tags) >= 1: + for named_tag in upgrade_from_tags: + if named_tag not in named_tag_found_in_config: + continue + resave_record_path_sets, executables_call_set = _perform_upgrade( + upgrade_config=upgrade_configs.get(named_tag), + resave_record_path_sets=resave_record_path_sets, + executables_call_set=executables_call_set, + tag=named_tag, + ) + if len(resave_record_path_sets) >= 1 or len(executables_call_set) >= 1: + to_update_flag = True + for record_path in resave_record_path_sets: + try: + rec_type = ClassLoader.load_class(record_path) + except ClassNotFoundError as err: + raise UpgradeError(f"Unknown Record type {record_path}") from err + if not issubclass(rec_type, BaseRecord): + raise UpgradeError( + f"Only BaseRecord can be resaved, found: {str(rec_type)}" + ) + async with root_profile.session() as session: + all_records = await rec_type.query(session) + for record in all_records: + await record.save( + session, + reason="re-saving record during the upgrade process", + ) + if len(all_records) == 0: + LOGGER.info(f"No records of {str(rec_type)} found") + else: + LOGGER.info(f"All recs of {str(rec_type)} successfully re-saved") + for callable_name in executables_call_set: + _callable = version_upgrade_config_inst.get_callable(callable_name) + if not _callable: + raise UpgradeError(f"No function specified for {callable_name}") + await _callable(root_profile) # Update storage version if to_update_flag: @@ -394,6 +443,67 @@ async def update_existing_records(profile: Profile): pass +########################################################## +# Fix for ACA-Py Issue #2485 +# issuance_type attribue in IssuerRevRegRecord was removed +# in 0.5.3 version. IssuerRevRegRecord created previously +# will need +########################################################## + + +async def find_affected_issue_rev_reg_records( + session: ProfileSession, +) -> Sequence[RecordType]: + """Get IssuerRevRegRecord records with issuance_type for re-saving. + + Args: + session: The profile session to use + """ + storage = session.inject(BaseStorage) + rows = await storage.find_all_records( + IssuerRevRegRecord.RECORD_TYPE, + ) + issue_rev_reg_records_to_update = [] + for record in rows: + vals = json.loads(record.value) + to_update = False + try: + record_id = record.id + record_id_name = IssuerRevRegRecord.RECORD_ID_NAME + if record_id_name in vals: + raise ValueError(f"Duplicate {record_id_name} inputs; {vals}") + params = dict(**vals) + # Check for issuance_type and add record_id for later tracking + if "issuance_type" in params: + LOGGER.info( + f"IssuerRevRegRecord {record_id} tagged for fixing issuance_type." + ) + del params["issuance_type"] + to_update = True + params[record_id_name] = record_id + if to_update: + issue_rev_reg_records_to_update.append(IssuerRevRegRecord(**params)) + except BaseModelError as err: + raise BaseModelError(f"{err}, for record id {record.id}") + return issue_rev_reg_records_to_update + + +async def fix_issue_rev_reg_records(profile: Profile): + """Update IssuerRevRegRecord records. + + Args: + profile: Root profile + + """ + async with profile.session() as session: + issue_rev_reg_records = await find_affected_issue_rev_reg_records(session) + for record in issue_rev_reg_records: + await record.save( + session, + reason="re-saving issue_rev_reg record without issuance type", + ) + + def execute(argv: Sequence[str] = None): """Entrypoint.""" parser = arg.create_argument_parser(prog=PROG) @@ -413,7 +523,8 @@ def main(): UPGRADE_EXISTING_RECORDS_FUNCTION_MAPPING = { - "update_existing_records": update_existing_records + "update_existing_records": update_existing_records, + "fix_issue_rev_reg_records": fix_issue_rev_reg_records, } main() diff --git a/aries_cloudagent/config/argparse.py b/aries_cloudagent/config/argparse.py index ac38895254..5167831c32 100644 --- a/aries_cloudagent/config/argparse.py +++ b/aries_cloudagent/config/argparse.py @@ -2139,6 +2139,13 @@ def add_arguments(self, parser: ArgumentParser): ), ) + parser.add_argument( + "--named-tag", + action="append", + env_var="ACAPY_UPGRADE_NAMED_TAGS", + help=("Runs upgrade steps associated with tags provided in the config"), + ) + def get_settings(self, args: Namespace) -> dict: """Extract ACA-Py upgrade process settings.""" settings = {} @@ -2148,4 +2155,8 @@ def get_settings(self, args: Namespace) -> dict: settings["upgrade.from_version"] = args.from_version if args.force_upgrade: settings["upgrade.force_upgrade"] = args.force_upgrade + if args.named_tag: + settings["upgrade.named_tags"] = ( + list(args.named_tag) if args.named_tag else [] + ) return settings diff --git a/aries_cloudagent/revocation/models/issuer_rev_reg_record.py b/aries_cloudagent/revocation/models/issuer_rev_reg_record.py index ccb47e63ed..a027010937 100644 --- a/aries_cloudagent/revocation/models/issuer_rev_reg_record.py +++ b/aries_cloudagent/revocation/models/issuer_rev_reg_record.py @@ -1,6 +1,7 @@ """Issuer revocation registry storage handling.""" import json +import importlib import logging import uuid from functools import total_ordering @@ -13,6 +14,10 @@ from marshmallow import fields, validate from ...core.profile import Profile, ProfileSession +from ...indy.credx.issuer import ( + CATEGORY_CRED_DEF, + CATEGORY_REV_REG_DEF_PRIVATE, +) from ...indy.issuer import IndyIssuer, IndyIssuerError from ...indy.models.revocation import ( IndyRevRegDef, @@ -379,41 +384,59 @@ async def fix_ledger_entry( session, rev_reg_id=self.revoc_reg_id ) - revoked_ids = [] - for rec in recs: - if rec.state == IssuerCredRevRecord.STATE_REVOKED: - revoked_ids.append(int(rec.cred_rev_id)) - if int(rec.cred_rev_id) not in rev_reg_delta["value"]["revoked"]: - # await rec.set_state(session, IssuerCredRevRecord.STATE_ISSUED) - rec_count += 1 - - LOGGER.debug(">>> fixed entry recs count = %s", rec_count) - LOGGER.debug( - ">>> rev_reg_record.revoc_reg_entry.value: %s", - self.revoc_reg_entry.value, + revoked_ids = [] + for rec in recs: + if rec.state == IssuerCredRevRecord.STATE_REVOKED: + revoked_ids.append(int(rec.cred_rev_id)) + if int(rec.cred_rev_id) not in rev_reg_delta["value"]["revoked"]: + # await rec.set_state(session, IssuerCredRevRecord.STATE_ISSUED) + rec_count += 1 + + LOGGER.debug(">>> fixed entry recs count = %s", rec_count) + LOGGER.debug( + ">>> rev_reg_record.revoc_reg_entry.value: %s", + self.revoc_reg_entry.value, + ) + LOGGER.debug('>>> rev_reg_delta.get("value"): %s', rev_reg_delta.get("value")) + + # if we had any revocation discrepencies, check the accumulator value + if rec_count > 0: + if (self.revoc_reg_entry.value and rev_reg_delta.get("value")) and not ( + self.revoc_reg_entry.value.accum == rev_reg_delta["value"]["accum"] + ): + # self.revoc_reg_entry = rev_reg_delta["value"] + # await self.save(session) + accum_count += 1 + async with profile.session() as session: + issuer_rev_reg_record = ( + await IssuerRevRegRecord.retrieve_by_revoc_reg_id( + session, self.revoc_reg_id + ) + ) + cred_def_id = issuer_rev_reg_record.cred_def_id + _cred_def = await session.handle.fetch(CATEGORY_CRED_DEF, cred_def_id) + _rev_reg_def_private = await session.handle.fetch( + CATEGORY_REV_REG_DEF_PRIVATE, self.revoc_reg_id + ) + credx_module = importlib.import_module("indy_credx") + cred_defn = credx_module.CredentialDefinition.load(_cred_def.value_json) + rev_reg_defn_private = ( + credx_module.RevocationRegistryDefinitionPrivate.load( + _rev_reg_def_private.value_json + ) ) - LOGGER.debug( - '>>> rev_reg_delta.get("value"): %s', rev_reg_delta.get("value") + calculated_txn = await generate_ledger_rrrecovery_txn( + genesis_transactions, + self.revoc_reg_id, + revoked_ids, + cred_defn, + rev_reg_defn_private, ) + recovery_txn = json.loads(calculated_txn.to_json()) - # if we had any revocation discrepencies, check the accumulator value - if rec_count > 0: - if (self.revoc_reg_entry.value and rev_reg_delta.get("value")) and not ( - self.revoc_reg_entry.value.accum == rev_reg_delta["value"]["accum"] - ): - # self.revoc_reg_entry = rev_reg_delta["value"] - # await self.save(session) - accum_count += 1 - - calculated_txn = await generate_ledger_rrrecovery_txn( - genesis_transactions, - self.revoc_reg_id, - revoked_ids, - ) - recovery_txn = json.loads(calculated_txn.to_json()) - - LOGGER.debug(">>> apply_ledger_update = %s", apply_ledger_update) - if apply_ledger_update: + LOGGER.debug(">>> apply_ledger_update = %s", apply_ledger_update) + if apply_ledger_update: + async with profile.session() as session: ledger = session.inject_or(BaseLedger) if not ledger: reason = "No ledger available" @@ -426,7 +449,7 @@ async def fix_ledger_entry( self.revoc_reg_id, "CL_ACCUM", recovery_txn ) - applied_txn = ledger_response["result"] + applied_txn = ledger_response["result"] return (rev_reg_delta, recovery_txn, applied_txn) diff --git a/aries_cloudagent/revocation/models/tests/test_issuer_rev_reg_record.py b/aries_cloudagent/revocation/models/tests/test_issuer_rev_reg_record.py index 80d15592ea..bc292daeb4 100644 --- a/aries_cloudagent/revocation/models/tests/test_issuer_rev_reg_record.py +++ b/aries_cloudagent/revocation/models/tests/test_issuer_rev_reg_record.py @@ -1,10 +1,14 @@ +import importlib import json from os.path import join +from typing import Any, Mapping, Type from asynctest import TestCase as AsyncTestCase, mock as async_mock -from ....core.in_memory import InMemoryProfile +from ....core.in_memory import InMemoryProfile, InMemoryProfileSession +from ....core.profile import Profile, ProfileSession +from ....config.injection_context import InjectionContext from ....indy.issuer import IndyIssuer, IndyIssuerError from ....indy.util import indy_client_dir from ....ledger.base import BaseLedger @@ -78,6 +82,191 @@ async def test_order(self): await rec1.save(session, reason="another record") assert rec0 < rec1 + async def test_fix_ledger_entry(self): + mock_cred_def = { + "ver": "1.0", + "id": "55GkHamhTU1ZbTbV2ab9DE:3:CL:15:tag", + "schemaId": "15", + "type": "CL", + "tag": "tag", + "value": { + "primary": { + "n": "97755077532337293488745809101840506138839796255951676757957095601046950252131007247574478925999370347259264200571203215922043340992124710162716447600735862203372184211636922189157817784130889637892277882125570534881418101997106854432924131454003284028861730869486619833238782461572414320131411215654018054873708704239239964147885524705957168226001184349267512368273047027640698617139590254809517853853130886625330234652284599369944951191468504513070325577275942459149020744004320427886430812173881305323670033871442431456536710164496981741573947141839358296893048748871620778358547174947418982842584086910766377426381", + "s": "93857461424756524831309204327541325286955427618197438652468158979904850495412765053870776608842922458588066684799662534588058064651312983375745531372709501399468055860133002734655992193122962210761579825894255323571233899698407599997585154150003119067126768230932260880730480485869205348502571475803409653237711195597513501909794351191631697504002513031303493339100149587811674358458553310268294288630892274988762794340699542957746255964836213015732087927295229891638172796974267746722775815971385993320758281434138548295910631509522670548769524207136946079991721576359187532370389806940124963337347442220291195381379", + "r": { + "attr": "47851812221002419738510348434589719299005636707459946297826216114810122000929029718256700990932773158900801032759636000731156275563407397654401429007893443548977293352519606768993036568324588287173469229957113813985349265315838049185336810444812307980611094706266979152047930521019494220669875858786312470775883694491178666122652050631237335108908878703921025524371908258656719798575775820756662770857250313841640739199537587617643616110506607528089021428997787577846819067855712454919148492951257992078447468260961148762693304228013316282905649275245604958548728173709772269908692834231109401462806738322298320014588", + "master_secret": "1456034034452917866826058280183727425788793902981406386175016190082488912409448091384258390876182506491481376203626852147024966574074505159095019883608712317385148607657338114099655296899988391276120507283428334300992065352980582993987058928311851237491145495605817715962285966152190021168736714801270287839185496030660074232737687865821410617700173791873244132463428232777257496952831001540198725563550215668732380420651897876237788070219793125826917973396212392661183288071986411608233468079014270033759863563636650489933951357555474357165922955337708555697826065745245669404874498188819137127409608428703992429087", + }, + "rctxt": "3181166664902801317779556377385630436151550717862204865421515198259158965590304479081007449054293128232193810709014084698474265818919401580417293157753663769438333622403413264724381527519123803324371803790394771682351074853790156764071298806108016312946683322202825645967662223488370365263607749486727675784672879635222504232881959377264213229748333060407839919218390751977625946072140500297691925789411870206929445018192788803161174534714033652405735420578422669164795825360682590769380466620583112381320768898271838621002245390378640966896034356391997262998321678222800285339726066703530463747958328257763842981029", + "z": "88882571993858813307170085778645601203164536169839951148860666045278872319305671063809074514017614163768224290285339562133145800179699201796197881847784025241295744277627025401945924707873790519590490294771194748427422972923806879605569348426345486261878031328212177481424474160598821738627280954513834712377303213119700398636139443890766009925681362795514758039170870017744796137650484010207378636277505131056606153648732287440383301414486959605962454376715897921966027018433464774363787319454306102402364604589381426633687528444586300536019182461458586285601075166524298872635741326726895166696312685388693673327358", + }, + "revocation": { + "g": "1 1ACDF1F880164FAE240C033DD2CC7E80130264E1A3BC81A3B2BD28E65827E1DA 1 0A4DD8DBB73B2A26CD2A576ECC0C5AB609346EDC923FFF20685EBE3B618A841A 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8", + "g_dash": "1 206CF4DECDE37A09315EE453D08E64CE6EFF4B89D6FCADFB456396AD1C57C442 1 14E36B42881BD9CA04608002810B4C9923BCC52D379A8F216FA57C6392A3BE83 1 138F620C8EEBFEAF9D1EF90152A11B9C0BDF3AED384324767E2293A4FC12F784 1 073227CD0CC0A6D5101A3CE03BDF6D29E6BACB33A090A4AE95F688F45BD54316 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000", + "h": "1 0B608579F0A9A830862E45AC4E97F5CB30FD118379A1C408363D92970DBE989C 1 1671922BCECA4E69B693851EA3C78CB3B2714CACAAB64DD5B5CF3C5A187A572A 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8", + "h0": "1 1BCE3325E76EF9FA7566732BD17B5B82B1D0E9559B393D368A0BD09761AE2A39 1 10A1DA70F60369FBD69742B1564E98C85188312CFCCDD8AD5D11978F3FA1376D 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8", + "h1": "1 145B2DB05BFAAB9AF90401897E318B73BCEE7178D2857A551561DE0893098137 1 17A23E4161EF4392A56F13B2B0F40E0FA1DD28CDBD3FF33FDD67C5D998A10B6D 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8", + "h2": "1 14E18A1496B043246A4A860159EFCF49621606AF3E46A9854B8949E9F8C0FD97 1 1EFE5D486448A8F7A44BE358B674C0CF6FF09AAFDDDA416462943311CCA1A291 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8", + "htilde": "1 152FBD85CD4C81796599582D2CFE37407760B348A7816ADE2C9B76077C62440D 1 23DDDD15C99111413585FD282E05095FC27803EA8DCF94570B3FD2A53658AE00 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8", + "h_cap": "1 06333FED8C082FE894D02CC04A53B78D801ABC9CE09D0C5B731FEDF82A1BBB5F 1 06FD92996796D6B81F7803C3CEA3891741552B452E3F6C735F5BFE096234442A 1 23C5C9462697381BDF16658D133E5B0888C909E0EA01F041FEDC8E3A4F84BBF6 1 0511A0E8510AAF8454EB78446C563A8719622DADFF4AE86DF07E2969D2E4E48F 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000", + "u": "1 1566C7AC1F99896C4E0CD7BD1B77C241614CE03337201FC6BB886650D78AC20D 1 1C73D8607361E539355F39297C3D8073B6CCA2DBF8A7AC50A50BF2FF20CAD7E9 1 1BF4F8AAE80EFAE308FF9ED399F24546306623EE8BECF6A2B39D939B1FCE94DA 1 0646620C014AD0A78BF83748F165211EFF811B526859F71AD184DA462A0C9303 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000", + "pk": "1 16373692E1B40E44D2010B1CC7023E89E96DD9F45DB3F837D532A1299E80DB23 1 0A0DA35D2830848D4C04FADBB3E726525ADE9DC5F3364AB6286F642E904BB913 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8", + "y": "1 1C132346B6D6999F872132568ACB6F722FF4F93E08F0F6A1433E25F0937FBCB8 1 093034020F4F2087FDAA63D2EBD9BC02A7FF88F914F5971F285CD21BC200F9BD 1 04FB2599E6FA39E12F7F4A9B1C152E753E6584755843D7B6F746904A78396650 1 2291AC4F56DC5E0FEA5C580ACC2521714A3A8F3C79D78D475AEC473FB631E131 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000", + }, + }, + } + mock_reg_rev_def_private = { + "value": { + "gamma": "11D1EF7D29B67A898E8BFDECEF24AD3B23A4984636F31AA84E1958A724A3579A" + } + } + + class TestProfile(InMemoryProfile): + def session(self, context: InjectionContext = None) -> "ProfileSession": + return TestProfileSession(self, context=context) + + @classmethod + def test_profile( + cls, settings: Mapping[str, Any] = None, bind: Mapping[Type, Any] = None + ) -> "TestProfile": + profile = TestProfile( + context=InjectionContext(enforce_typing=False, settings=settings), + name=InMemoryProfile.TEST_PROFILE_NAME, + ) + if bind: + for k, v in bind.items(): + if v: + profile.context.injector.bind_instance(k, v) + else: + profile.context.injector.clear_binding(k) + return profile + + @classmethod + def test_session( + cls, settings: Mapping[str, Any] = None, bind: Mapping[Type, Any] = None + ) -> "TestProfileSession": + session = TestProfileSession(cls.test_profile(), settings=settings) + session._active = True + session._init_context() + if bind: + for k, v in bind.items(): + if v: + session.context.injector.bind_instance(k, v) + else: + session.context.injector.clear_binding(k) + return session + + class TestProfileSession(InMemoryProfileSession): + def __init__( + self, + profile: Profile, + *, + context: InjectionContext = None, + settings: Mapping[str, Any] = None, + ): + super().__init__(profile=profile, context=context, settings=settings) + self.handle_counter = 0 + + @property + def handle(self): + if self.handle_counter == 0: + self.handle_counter = self.handle_counter + 1 + return async_mock.MagicMock( + fetch=async_mock.CoroutineMock( + return_value=async_mock.MagicMock( + value_json=json.dumps(mock_cred_def) + ) + ) + ) + else: + return async_mock.MagicMock( + fetch=async_mock.CoroutineMock( + return_value=async_mock.MagicMock( + value_json=json.dumps(mock_reg_rev_def_private), + ), + ) + ) + + credx_module = importlib.import_module("indy_credx") + rev_reg_delta = credx_module.RevocationRegistryDelta.load( + json.dumps( + { + "ver": "1.0", + "value": { + "accum": "1 0792BD1C8C1A529173FDF54A5B30AC90C2472956622E9F04971D36A9BF77C2C5 1 13B18B6B68AD62605C74FD61088814338EDEEB41C2195F96EC0E83B2B3D0258F 1 102ED0DDE96F6367199CE1C0B138F172BC913B65E37250581606974034F4CA20 1 1C53786D2C15190B57167CDDD2A046CAD63970B5DE43F4D492D4F46B8EEE6FF1 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000" + }, + } + ) + ) + + TEST_GENESIS_TXN = "test_genesis_txn" + rec = IssuerRevRegRecord( + issuer_did=TEST_DID, + revoc_reg_id=REV_REG_ID, + revoc_reg_def=REV_REG_DEF, + revoc_def_type="CL_ACCUM", + revoc_reg_entry=REV_REG_ENTRY, + cred_def_id=CRED_DEF_ID, + tails_public_uri=TAILS_URI, + ) + _test_rev_reg_delta = { + "ver": "1.0", + "value": {"accum": "ACCUM", "issued": [1, 2], "revoked": [3, 4]}, + } + self.ledger.get_revoc_reg_delta = async_mock.CoroutineMock( + return_value=( + _test_rev_reg_delta, + 1234567890, + ) + ) + self.ledger.send_revoc_reg_entry = async_mock.CoroutineMock( + return_value={ + "result": {"...": "..."}, + }, + ) + _test_session = TestProfile.test_session( + settings={"tails_server_base_url": "http://1.2.3.4:8088"}, + ) + _test_profile = _test_session.profile + _test_profile.context.injector.bind_instance(BaseLedger, self.ledger) + with async_mock.patch.object( + test_module.IssuerCredRevRecord, + "query_by_ids", + async_mock.CoroutineMock( + return_value=[ + test_module.IssuerCredRevRecord( + record_id=test_module.UUID4_EXAMPLE, + state=test_module.IssuerCredRevRecord.STATE_REVOKED, + cred_ex_id=test_module.UUID4_EXAMPLE, + rev_reg_id=REV_REG_ID, + cred_rev_id="1", + ) + ] + ), + ), async_mock.patch.object( + test_module.IssuerRevRegRecord, + "retrieve_by_revoc_reg_id", + async_mock.CoroutineMock(return_value=rec), + ), async_mock.patch.object( + test_module, + "generate_ledger_rrrecovery_txn", + async_mock.CoroutineMock(return_value=rev_reg_delta), + ): + assert ( + _test_rev_reg_delta, + { + "ver": "1.0", + "value": { + "accum": "1 0792BD1C8C1A529173FDF54A5B30AC90C2472956622E9F04971D36A9BF77C2C5 1 13B18B6B68AD62605C74FD61088814338EDEEB41C2195F96EC0E83B2B3D0258F 1 102ED0DDE96F6367199CE1C0B138F172BC913B65E37250581606974034F4CA20 1 1C53786D2C15190B57167CDDD2A046CAD63970B5DE43F4D492D4F46B8EEE6FF1 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000" + }, + }, + {"...": "..."}, + ) == await rec.fix_ledger_entry( + profile=_test_profile, + apply_ledger_update=True, + genesis_transactions=json.dumps(TEST_GENESIS_TXN), + ) + async def test_generate_registry_etc(self): rec = IssuerRevRegRecord( issuer_did=TEST_DID, diff --git a/aries_cloudagent/revocation/recover.py b/aries_cloudagent/revocation/recover.py index 49adcbc5ed..f2bf38267c 100644 --- a/aries_cloudagent/revocation/recover.py +++ b/aries_cloudagent/revocation/recover.py @@ -85,7 +85,9 @@ async def fetch_txns(genesis_txns, registry_id): return defn, registry, delta, revoked, tails_temp -async def generate_ledger_rrrecovery_txn(genesis_txns, registry_id, set_revoked): +async def generate_ledger_rrrecovery_txn( + genesis_txns, registry_id, set_revoked, cred_def, rev_reg_def_private +): """Generate a new ledger accum entry, based on wallet vs ledger revocation state.""" new_delta = None @@ -111,7 +113,9 @@ async def generate_ledger_rrrecovery_txn(genesis_txns, registry_id, set_revoked) LOGGER.debug("tails_temp: %s", tails_temp.name) update_registry = registry.copy() - new_delta = update_registry.update(defn, [], updates, tails_temp.name) + new_delta = update_registry.update( + cred_def, defn, rev_reg_def_private, [], updates + ) LOGGER.debug("New delta:") LOGGER.debug(new_delta.to_json()) From 387fd4784a8e045e61c7fafb09bb5967a1f0844c Mon Sep 17 00:00:00 2001 From: Stephen Curran Date: Tue, 26 Sep 2023 21:52:55 +0000 Subject: [PATCH 75/85] Change arg_parse to always set --emit-new-didcomm-prefix and --emit-new-didcomm-mime-type to true Signed-off-by: Stephen Curran --- aries_cloudagent/config/argparse.py | 14 ++++++++------ .../protocols/tests/test_didcomm_prefix.py | 2 ++ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/aries_cloudagent/config/argparse.py b/aries_cloudagent/config/argparse.py index ac38895254..0dbe1a1620 100644 --- a/aries_cloudagent/config/argparse.py +++ b/aries_cloudagent/config/argparse.py @@ -1207,7 +1207,8 @@ def add_arguments(self, parser: ArgumentParser): help=( "Emit protocol messages with new DIDComm prefix; i.e., " "'https://didcomm.org/' instead of (default) prefix " - "'did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/'." + "'did:sov:BzCbsNYhMrjHiqZDTUASHg;spec/'. " + "Forced to `true` as the old prefix must never be used." ), ) parser.add_argument( @@ -1217,7 +1218,8 @@ def add_arguments(self, parser: ArgumentParser): help=( "Send packed agent messages with the DIDComm MIME type " "as of RFC 0044; i.e., 'application/didcomm-envelope-enc' " - "instead of 'application/ssi-agent-wire'." + "instead of 'application/ssi-agent-wire'. " + "Forced to `true` as the old MIME type must never be used." ), ) parser.add_argument( @@ -1288,10 +1290,10 @@ def get_settings(self, args: Namespace) -> dict: raise ArgsParseError("Error writing trace event " + str(e)) if args.preserve_exchange_records: settings["preserve_exchange_records"] = True - if args.emit_new_didcomm_prefix: - settings["emit_new_didcomm_prefix"] = True - if args.emit_new_didcomm_mime_type: - settings["emit_new_didcomm_mime_type"] = True + # NOT setting the following two parameters `True` is no longer supported + # Even if the args are not set, the config setting is True. + settings["emit_new_didcomm_prefix"] = True + settings["emit_new_didcomm_mime_type"] = True if args.exch_use_unencrypted_tags: settings["exch_use_unencrypted_tags"] = True environ["EXCH_UNENCRYPTED_TAGS"] = "True" diff --git a/aries_cloudagent/protocols/tests/test_didcomm_prefix.py b/aries_cloudagent/protocols/tests/test_didcomm_prefix.py index be0dd6b6dd..4328d539d0 100644 --- a/aries_cloudagent/protocols/tests/test_didcomm_prefix.py +++ b/aries_cloudagent/protocols/tests/test_didcomm_prefix.py @@ -16,6 +16,8 @@ def test_didcomm_prefix(self): f"{DIDCommPrefix.NEW.value}/hello" ) + # No longer possible to have the arg `False` but leaving in test + # Still want to be able to receive the OLD format, just not emit it DIDCommPrefix.set({"emit_new_didcomm_prefix": False}) assert environ.get("DIDCOMM_PREFIX") == DIDCommPrefix.OLD.value assert DIDCommPrefix.qualify_current("hello") == ( From 1ec6cc5fbe120ae7ef903784cb2bb78d278b10d3 Mon Sep 17 00:00:00 2001 From: Rob Simpson Date: Tue, 26 Sep 2023 18:34:02 -0500 Subject: [PATCH 76/85] fix: Problem Report Before Connection Signed-off-by: Rob Simpson --- .../protocols/issue_credential/v2_0/routes.py | 21 ------------------- 1 file changed, 21 deletions(-) diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/routes.py b/aries_cloudagent/protocols/issue_credential/v2_0/routes.py index 721128b4ae..6ef8697de0 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/routes.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/routes.py @@ -835,13 +835,6 @@ async def credential_exchange_send_proposal(request: web.BaseRequest): if cred_ex_record: async with profile.session() as session: await cred_ex_record.save_error_state(session, reason=err.roll_up) - await report_problem( - err, - ProblemReportReason.ISSUANCE_ABANDONED.value, - web.HTTPBadRequest, - cred_ex_record or conn_record, - outbound_handler, - ) await outbound_handler(cred_proposal_message, connection_id=connection_id) @@ -1042,13 +1035,6 @@ async def credential_exchange_send_free_offer(request: web.BaseRequest): if cred_ex_record: async with profile.session() as session: await cred_ex_record.save_error_state(session, reason=err.roll_up) - await report_problem( - err, - ProblemReportReason.ISSUANCE_ABANDONED.value, - web.HTTPBadRequest, - cred_ex_record or conn_record, - outbound_handler, - ) await outbound_handler(cred_offer_message, connection_id=connection_id) @@ -1252,13 +1238,6 @@ async def credential_exchange_send_free_request(request: web.BaseRequest): if cred_ex_record: async with profile.session() as session: await cred_ex_record.save_error_state(session, reason=err.roll_up) - await report_problem( - err, - ProblemReportReason.ISSUANCE_ABANDONED.value, - web.HTTPBadRequest, - cred_ex_record, - outbound_handler, - ) await outbound_handler(cred_request_message, connection_id=connection_id) From 90ba448e199811be5ce7da86a957d6933b2e9629 Mon Sep 17 00:00:00 2001 From: Rob Simpson Date: Tue, 26 Sep 2023 18:55:44 -0500 Subject: [PATCH 77/85] fix: Problem Report Before Exchange Signed-off-by: Rob Simpson --- .../protocols/issue_credential/v2_0/routes.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/routes.py b/aries_cloudagent/protocols/issue_credential/v2_0/routes.py index 6ef8697de0..431990d4da 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/routes.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/routes.py @@ -836,7 +836,8 @@ async def credential_exchange_send_proposal(request: web.BaseRequest): async with profile.session() as session: await cred_ex_record.save_error_state(session, reason=err.roll_up) - await outbound_handler(cred_proposal_message, connection_id=connection_id) + else: + await outbound_handler(cred_proposal_message, connection_id=connection_id) trace_event( context.settings, @@ -1036,7 +1037,8 @@ async def credential_exchange_send_free_offer(request: web.BaseRequest): async with profile.session() as session: await cred_ex_record.save_error_state(session, reason=err.roll_up) - await outbound_handler(cred_offer_message, connection_id=connection_id) + else: + await outbound_handler(cred_offer_message, connection_id=connection_id) trace_event( context.settings, @@ -1239,7 +1241,8 @@ async def credential_exchange_send_free_request(request: web.BaseRequest): async with profile.session() as session: await cred_ex_record.save_error_state(session, reason=err.roll_up) - await outbound_handler(cred_request_message, connection_id=connection_id) + else: + await outbound_handler(cred_request_message, connection_id=connection_id) trace_event( context.settings, From ad2fa1ca1573b753275080706bd4d9306200e99b Mon Sep 17 00:00:00 2001 From: Rob Simpson Date: Tue, 26 Sep 2023 19:20:09 -0500 Subject: [PATCH 78/85] fix: Problem Report Before Exchange Signed-off-by: Rob Simpson --- .../protocols/issue_credential/v1_0/routes.py | 28 ++++++------------- .../protocols/issue_credential/v2_0/routes.py | 3 ++ 2 files changed, 12 insertions(+), 19 deletions(-) diff --git a/aries_cloudagent/protocols/issue_credential/v1_0/routes.py b/aries_cloudagent/protocols/issue_credential/v1_0/routes.py index e00411efed..f15622b503 100644 --- a/aries_cloudagent/protocols/issue_credential/v1_0/routes.py +++ b/aries_cloudagent/protocols/issue_credential/v1_0/routes.py @@ -692,18 +692,13 @@ async def credential_exchange_send_proposal(request: web.BaseRequest): if cred_ex_record: async with profile.session() as session: await cred_ex_record.save_error_state(session, reason=err.roll_up) - await report_problem( - err, - ProblemReportReason.ISSUANCE_ABANDONED.value, - web.HTTPBadRequest, - cred_ex_record or connection_record, - outbound_handler, - ) + # other party cannot yet receive a problem report about our failed protocol start - await outbound_handler( - credential_proposal, - connection_id=connection_id, - ) + else: + await outbound_handler( + credential_proposal, + connection_id=connection_id, + ) trace_event( context.settings, @@ -903,15 +898,10 @@ async def credential_exchange_send_free_offer(request: web.BaseRequest): if cred_ex_record: async with profile.session() as session: await cred_ex_record.save_error_state(session, reason=err.roll_up) - await report_problem( - err, - ProblemReportReason.ISSUANCE_ABANDONED.value, - web.HTTPBadRequest, - cred_ex_record or connection_record, - outbound_handler, - ) + # other party cannot yet receive a problem report about our failed protocol start - await outbound_handler(credential_offer_message, connection_id=connection_id) + else: + await outbound_handler(credential_offer_message, connection_id=connection_id) trace_event( context.settings, diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/routes.py b/aries_cloudagent/protocols/issue_credential/v2_0/routes.py index 431990d4da..28827a5289 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/routes.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/routes.py @@ -835,6 +835,7 @@ async def credential_exchange_send_proposal(request: web.BaseRequest): if cred_ex_record: async with profile.session() as session: await cred_ex_record.save_error_state(session, reason=err.roll_up) + # other party cannot yet receive a problem report about our failed protocol start else: await outbound_handler(cred_proposal_message, connection_id=connection_id) @@ -1036,6 +1037,7 @@ async def credential_exchange_send_free_offer(request: web.BaseRequest): if cred_ex_record: async with profile.session() as session: await cred_ex_record.save_error_state(session, reason=err.roll_up) + # other party cannot yet receive a problem report about our failed protocol start else: await outbound_handler(cred_offer_message, connection_id=connection_id) @@ -1240,6 +1242,7 @@ async def credential_exchange_send_free_request(request: web.BaseRequest): if cred_ex_record: async with profile.session() as session: await cred_ex_record.save_error_state(session, reason=err.roll_up) + # other party cannot yet receive a problem report about our failed protocol start else: await outbound_handler(cred_request_message, connection_id=connection_id) From 7f877ff6f45cf18fdb4259754c6b54a825e943e3 Mon Sep 17 00:00:00 2001 From: Rob Simpson Date: Tue, 26 Sep 2023 19:45:19 -0500 Subject: [PATCH 79/85] fix: Problem Report Before Exchange Signed-off-by: Rob Simpson --- aries_cloudagent/protocols/issue_credential/v1_0/routes.py | 6 +++++- aries_cloudagent/protocols/issue_credential/v2_0/routes.py | 6 ++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/aries_cloudagent/protocols/issue_credential/v1_0/routes.py b/aries_cloudagent/protocols/issue_credential/v1_0/routes.py index f15622b503..dc89bfdc2c 100644 --- a/aries_cloudagent/protocols/issue_credential/v1_0/routes.py +++ b/aries_cloudagent/protocols/issue_credential/v1_0/routes.py @@ -668,6 +668,8 @@ async def credential_exchange_send_proposal(request: web.BaseRequest): connection_record = None cred_ex_record = None + credential_proposal = None + result = None try: preview = CredentialPreview.deserialize(preview_spec) if preview_spec else None async with profile.session() as session: @@ -693,7 +695,7 @@ async def credential_exchange_send_proposal(request: web.BaseRequest): async with profile.session() as session: await cred_ex_record.save_error_state(session, reason=err.roll_up) # other party cannot yet receive a problem report about our failed protocol start - + else: await outbound_handler( credential_proposal, @@ -871,6 +873,8 @@ async def credential_exchange_send_free_offer(request: web.BaseRequest): cred_ex_record = None connection_record = None + credential_offer_message = None + result = None try: async with profile.session() as session: connection_record = await ConnRecord.retrieve_by_id(session, connection_id) diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/routes.py b/aries_cloudagent/protocols/issue_credential/v2_0/routes.py index 28827a5289..f7a671aa22 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/routes.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/routes.py @@ -805,6 +805,8 @@ async def credential_exchange_send_proposal(request: web.BaseRequest): conn_record = None cred_ex_record = None + cred_proposal_message = None + result = None try: cred_preview = ( V20CredPreview.deserialize(preview_spec) if preview_spec else None @@ -1006,6 +1008,8 @@ async def credential_exchange_send_free_offer(request: web.BaseRequest): cred_ex_record = None conn_record = None + cred_offer_message = None + result = None try: async with profile.session() as session: conn_record = await ConnRecord.retrieve_by_id(session, connection_id) @@ -1198,6 +1202,8 @@ async def credential_exchange_send_free_request(request: web.BaseRequest): conn_record = None cred_ex_record = None + cred_request_message = None + result = None try: try: async with profile.session() as session: From f68914b16f2f4599834c06a2b6e9ecf3dbbcd76f Mon Sep 17 00:00:00 2001 From: Rob Simpson Date: Tue, 26 Sep 2023 19:47:40 -0500 Subject: [PATCH 80/85] fix: Problem Report Before Exchange Signed-off-by: Rob Simpson --- aries_cloudagent/protocols/issue_credential/v1_0/routes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aries_cloudagent/protocols/issue_credential/v1_0/routes.py b/aries_cloudagent/protocols/issue_credential/v1_0/routes.py index dc89bfdc2c..beebd0b6ff 100644 --- a/aries_cloudagent/protocols/issue_credential/v1_0/routes.py +++ b/aries_cloudagent/protocols/issue_credential/v1_0/routes.py @@ -695,7 +695,7 @@ async def credential_exchange_send_proposal(request: web.BaseRequest): async with profile.session() as session: await cred_ex_record.save_error_state(session, reason=err.roll_up) # other party cannot yet receive a problem report about our failed protocol start - + else: await outbound_handler( credential_proposal, From 7283b43f275bb4ae05f00dd20ce810a2cfba21fb Mon Sep 17 00:00:00 2001 From: Rob Simpson Date: Tue, 26 Sep 2023 20:28:49 -0500 Subject: [PATCH 81/85] fix: Problem Report Before Exchange Signed-off-by: Rob Simpson --- .../protocols/issue_credential/v1_0/routes.py | 18 +++++++----------- .../protocols/issue_credential/v2_0/routes.py | 18 ++++++------------ 2 files changed, 13 insertions(+), 23 deletions(-) diff --git a/aries_cloudagent/protocols/issue_credential/v1_0/routes.py b/aries_cloudagent/protocols/issue_credential/v1_0/routes.py index beebd0b6ff..1e08f969fd 100644 --- a/aries_cloudagent/protocols/issue_credential/v1_0/routes.py +++ b/aries_cloudagent/protocols/issue_credential/v1_0/routes.py @@ -668,8 +668,6 @@ async def credential_exchange_send_proposal(request: web.BaseRequest): connection_record = None cred_ex_record = None - credential_proposal = None - result = None try: preview = CredentialPreview.deserialize(preview_spec) if preview_spec else None async with profile.session() as session: @@ -695,12 +693,12 @@ async def credential_exchange_send_proposal(request: web.BaseRequest): async with profile.session() as session: await cred_ex_record.save_error_state(session, reason=err.roll_up) # other party cannot yet receive a problem report about our failed protocol start + raise web.HTTPBadRequest(reason=err.roll_up) - else: - await outbound_handler( - credential_proposal, - connection_id=connection_id, - ) + await outbound_handler( + credential_proposal, + connection_id=connection_id, + ) trace_event( context.settings, @@ -873,8 +871,6 @@ async def credential_exchange_send_free_offer(request: web.BaseRequest): cred_ex_record = None connection_record = None - credential_offer_message = None - result = None try: async with profile.session() as session: connection_record = await ConnRecord.retrieve_by_id(session, connection_id) @@ -903,9 +899,9 @@ async def credential_exchange_send_free_offer(request: web.BaseRequest): async with profile.session() as session: await cred_ex_record.save_error_state(session, reason=err.roll_up) # other party cannot yet receive a problem report about our failed protocol start + raise web.HTTPBadRequest(reason=err.roll_up) - else: - await outbound_handler(credential_offer_message, connection_id=connection_id) + await outbound_handler(credential_offer_message, connection_id=connection_id) trace_event( context.settings, diff --git a/aries_cloudagent/protocols/issue_credential/v2_0/routes.py b/aries_cloudagent/protocols/issue_credential/v2_0/routes.py index f7a671aa22..d1f0995624 100644 --- a/aries_cloudagent/protocols/issue_credential/v2_0/routes.py +++ b/aries_cloudagent/protocols/issue_credential/v2_0/routes.py @@ -805,8 +805,6 @@ async def credential_exchange_send_proposal(request: web.BaseRequest): conn_record = None cred_ex_record = None - cred_proposal_message = None - result = None try: cred_preview = ( V20CredPreview.deserialize(preview_spec) if preview_spec else None @@ -838,9 +836,9 @@ async def credential_exchange_send_proposal(request: web.BaseRequest): async with profile.session() as session: await cred_ex_record.save_error_state(session, reason=err.roll_up) # other party cannot yet receive a problem report about our failed protocol start + raise web.HTTPBadRequest(reason=err.roll_up) - else: - await outbound_handler(cred_proposal_message, connection_id=connection_id) + await outbound_handler(cred_proposal_message, connection_id=connection_id) trace_event( context.settings, @@ -1008,8 +1006,6 @@ async def credential_exchange_send_free_offer(request: web.BaseRequest): cred_ex_record = None conn_record = None - cred_offer_message = None - result = None try: async with profile.session() as session: conn_record = await ConnRecord.retrieve_by_id(session, connection_id) @@ -1042,9 +1038,9 @@ async def credential_exchange_send_free_offer(request: web.BaseRequest): async with profile.session() as session: await cred_ex_record.save_error_state(session, reason=err.roll_up) # other party cannot yet receive a problem report about our failed protocol start + raise web.HTTPBadRequest(reason=err.roll_up) - else: - await outbound_handler(cred_offer_message, connection_id=connection_id) + await outbound_handler(cred_offer_message, connection_id=connection_id) trace_event( context.settings, @@ -1202,8 +1198,6 @@ async def credential_exchange_send_free_request(request: web.BaseRequest): conn_record = None cred_ex_record = None - cred_request_message = None - result = None try: try: async with profile.session() as session: @@ -1249,9 +1243,9 @@ async def credential_exchange_send_free_request(request: web.BaseRequest): async with profile.session() as session: await cred_ex_record.save_error_state(session, reason=err.roll_up) # other party cannot yet receive a problem report about our failed protocol start + raise web.HTTPBadRequest(reason=err.roll_up) - else: - await outbound_handler(cred_request_message, connection_id=connection_id) + await outbound_handler(cred_request_message, connection_id=connection_id) trace_event( context.settings, From 6bf13245c9bf6fcbe433a366637f047f7851a688 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Wed, 27 Sep 2023 12:13:29 -0700 Subject: [PATCH 82/85] fix concurrent sessions Signed-off-by: Andrew Whitehead --- aries_cloudagent/wallet/routes.py | 54 ++++++++++++++----------------- 1 file changed, 24 insertions(+), 30 deletions(-) diff --git a/aries_cloudagent/wallet/routes.py b/aries_cloudagent/wallet/routes.py index e440ba9469..ba5e7d4bd4 100644 --- a/aries_cloudagent/wallet/routes.py +++ b/aries_cloudagent/wallet/routes.py @@ -631,7 +631,6 @@ async def wallet_set_public_did(request: web.BaseRequest): """ context: AdminRequestContext = request["context"] - session = await context.session() outbound_handler = request["outbound_message_router"] @@ -653,9 +652,10 @@ async def wallet_set_public_did(request: web.BaseRequest): if not connection_id: raise web.HTTPBadRequest(reason="No endorser connection found") - wallet = session.inject_or(BaseWallet) - if not wallet: - raise web.HTTPForbidden(reason="No wallet available") + async with context.session() as session: + wallet = session.inject_or(BaseWallet) + if not wallet: + raise web.HTTPForbidden(reason="No wallet available") did = request.query.get("did") if not did: raise web.HTTPBadRequest(reason="Request query must include DID") @@ -677,9 +677,7 @@ async def wallet_set_public_did(request: web.BaseRequest): try: info, attrib_def = await promote_wallet_public_did( - context.profile, context, - context.session, did, write_ledger=write_ledger, connection_id=connection_id, @@ -725,9 +723,7 @@ async def wallet_set_public_did(request: web.BaseRequest): async def promote_wallet_public_did( - profile: Profile, context: AdminRequestContext, - session_fn, did: str, write_ledger: bool = False, connection_id: str = None, @@ -742,7 +738,7 @@ async def promote_wallet_public_did( # write only Indy DID write_ledger = is_indy_did and write_ledger - ledger = profile.inject_or(BaseLedger) + ledger = context.profile.inject_or(BaseLedger) if is_indy_did: if not ledger: @@ -756,30 +752,29 @@ async def promote_wallet_public_did( raise LookupError(f"DID {did} is not posted to the ledger") # check if we need to endorse - if is_author_role(profile): + if is_author_role(context.profile): # authors cannot write to the ledger write_ledger = False # author has not provided a connection id, so determine which to use if not connection_id: - connection_id = await get_endorser_connection_id(profile) + connection_id = await get_endorser_connection_id(context.profile) if not connection_id: raise web.HTTPBadRequest(reason="No endorser connection found") if not write_ledger: - try: - async with profile.session() as session: + async with context.session() as session: + try: connection_record = await ConnRecord.retrieve_by_id( session, connection_id ) - except StorageNotFoundError as err: - raise web.HTTPNotFound(reason=err.roll_up) from err - except BaseModelError as err: - raise web.HTTPBadRequest(reason=err.roll_up) from err - - async with profile.session() as session: + except StorageNotFoundError as err: + raise web.HTTPNotFound(reason=err.roll_up) from err + except BaseModelError as err: + raise web.HTTPBadRequest(reason=err.roll_up) from err endorser_info = await connection_record.metadata_get( session, "endorser_info" ) + if not endorser_info: raise web.HTTPForbidden( reason=( @@ -798,18 +793,16 @@ async def promote_wallet_public_did( did_info: DIDInfo = None attrib_def = None - async with session_fn() as session: + async with context.session() as session: wallet = session.inject_or(BaseWallet) did_info = await wallet.get_local_did(did) info = await wallet.set_public_did(did_info) - if info: - # Publish endpoint if necessary - endpoint = did_info.metadata.get("endpoint") + if info: + # Publish endpoint if necessary + endpoint = did_info.metadata.get("endpoint") - if is_indy_did and not endpoint: - async with session_fn() as session: - wallet = session.inject_or(BaseWallet) + if is_indy_did and not endpoint: endpoint = mediator_endpoint or context.settings.get("default_endpoint") attrib_def = await wallet.set_did_endpoint( info.did, @@ -820,9 +813,10 @@ async def promote_wallet_public_did( routing_keys=routing_keys, ) + if info: # Route the public DID - route_manager = profile.inject(RouteManager) - await route_manager.route_verkey(profile, info.verkey) + route_manager = context.profile.inject(RouteManager) + await route_manager.route_verkey(context.profile, info.verkey) return info, attrib_def @@ -1163,8 +1157,8 @@ async def on_register_nym_event(profile: Profile, event: Event): did = event.payload["did"] connection_id = event.payload.get("connection_id") try: - info, attrib_def = await promote_wallet_public_did( - profile, profile.context, profile.session, did, connection_id + _info, attrib_def = await promote_wallet_public_did( + profile.context, did, connection_id ) except Exception as err: # log the error, but continue From ced024a38cb1371fdb3c4cd4f47de457461fd4d5 Mon Sep 17 00:00:00 2001 From: Andrew Whitehead Date: Thu, 28 Sep 2023 09:43:53 -0700 Subject: [PATCH 83/85] avoid carrying open wallet instance in WalletKeyPair Signed-off-by: Andrew Whitehead --- .../v2_0/formats/ld_proof/handler.py | 5 +- .../present_proof/dif/pres_exch_handler.py | 52 +++++++---------- .../present_proof/v2_0/formats/dif/handler.py | 55 +++++++++--------- .../v2_0/formats/dif/tests/test_handler.py | 2 +- .../crypto/tests/test_wallet_key_pair.py | 56 +++++++++++-------- .../vc/ld_proofs/crypto/wallet_key_pair.py | 35 +++++++----- .../tests/test_bbs_bls_signature_2020.py | 6 +- .../test_bbs_bls_signature_proof_2020.py | 4 +- .../tests/test_ed25519_signature_2018.py | 4 +- .../tests/test_ed25519_signature_2020.py | 4 +- .../vc/ld_proofs/tests/test_ld_proofs.py | 16 +++--- .../vc/tests/test_bbs_mattr_interop.py | 6 +- aries_cloudagent/vc/vc_ld/tests/test_vc_ld.py | 20 +++---- 13 files changed, 130 insertions(+), 135 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 02231f134d..f7e2ff8a43 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 @@ -303,9 +303,6 @@ async def _get_suite( did_info: DIDInfo = None, ): """Get signature suite for issuance of verification.""" - session = await self.profile.session() - wallet = session.inject(BaseWallet) - # Get signature class based on proof type SignatureClass = PROOF_TYPE_SIGNATURE_SUITE_MAPPING[proof_type] @@ -314,7 +311,7 @@ async def _get_suite( verification_method=verification_method, proof=proof, key_pair=WalletKeyPair( - wallet=wallet, + profile=self.profile, key_type=SIGNATURE_SUITE_KEY_TYPE_MAPPING[SignatureClass], public_key_base58=did_info.verkey if did_info else None, ), 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 46492d8e87..5f70982eb2 100644 --- a/aries_cloudagent/protocols/present_proof/dif/pres_exch_handler.py +++ b/aries_cloudagent/protocols/present_proof/dif/pres_exch_handler.py @@ -115,7 +115,6 @@ def __init__( async def _get_issue_suite( self, *, - wallet: BaseWallet, issuer_id: str, ): """Get signature suite for signing presentation.""" @@ -139,17 +138,13 @@ async def _get_issue_suite( return SignatureClass( verification_method=verification_method, key_pair=WalletKeyPair( - wallet=wallet, + profile=self.profile, key_type=self.ISSUE_SIGNATURE_SUITE_KEY_TYPE_MAPPING[SignatureClass], public_key_base58=did_info.verkey if did_info else None, ), ) - async def _get_derive_suite( - self, - *, - wallet: BaseWallet, - ): + async def _get_derive_suite(self): """Get signature suite for deriving credentials.""" # Get signature class based on proof type SignatureClass = self.DERIVED_PROOF_TYPE_SIGNATURE_SUITE_MAPPING[ @@ -159,7 +154,7 @@ async def _get_derive_suite( # Generically create signature class return SignatureClass( key_pair=WalletKeyPair( - wallet=wallet, + profile=self.profile, key_type=self.DERIVE_SIGNATURE_SUITE_KEY_TYPE_MAPPING[SignatureClass], ), ) @@ -406,18 +401,14 @@ async def filter_constraints( new_credential_dict = self.reveal_doc( credential_dict=credential_dict, constraints=constraints ) - async with self.profile.session() as session: - wallet = session.inject(BaseWallet) - derive_suite = await self._get_derive_suite( - wallet=wallet, - ) - 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) + 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 @@ -1297,18 +1288,15 @@ async def create_vp( vp["presentation_submission"] = submission_property.serialize() if self.proof_type is BbsBlsSignature2020.signature_type: vp["@context"].append(SECURITY_CONTEXT_BBS_URL) - async with self.profile.session() as session: - wallet = session.inject(BaseWallet) - issue_suite = await self._get_issue_suite( - wallet=wallet, - issuer_id=issuer_id, - ) - signed_vp = await sign_presentation( - presentation=vp, - suite=issue_suite, - challenge=challenge, - document_loader=document_loader, - ) + 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] 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 868d7148bf..8a656368f8 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 @@ -21,7 +21,6 @@ WalletKeyPair, ) from ......vc.vc_ld.verify import verify_presentation -from ......wallet.base import BaseWallet from ......wallet.key_type import ED25519, BLS12381G2 from .....problem_report.v1_0.message import ProblemReport @@ -65,13 +64,13 @@ class DIFPresFormatHandler(V20PresFormatHandler): ISSUE_SIGNATURE_SUITE_KEY_TYPE_MAPPING[BbsBlsSignature2020] = BLS12381G2 ISSUE_SIGNATURE_SUITE_KEY_TYPE_MAPPING[BbsBlsSignatureProof2020] = BLS12381G2 - async def _get_all_suites(self, wallet: BaseWallet): + 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(wallet=wallet, key_type=key_type), + key_pair=WalletKeyPair(profile=self._profile, key_type=key_type), ) ) return suites @@ -471,33 +470,31 @@ async def verify_pres(self, pres_ex_record: V20PresExRecord) -> V20PresExRecord: presentation exchange record, updated """ - async with self._profile.session() as session: - wallet = session.inject(BaseWallet) - dif_proof = pres_ex_record.pres.attachment(DIFPresFormatHandler.format) - 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()) - if isinstance(dif_proof, Sequence): - for proof in dif_proof: - pres_ver_result = await verify_presentation( - presentation=proof, - suites=await self._get_all_suites(wallet=wallet), - document_loader=self._profile.inject(DocumentLoader), - challenge=challenge, - ) - if not pres_ver_result.verified: - break - else: + dif_proof = pres_ex_record.pres.attachment(DIFPresFormatHandler.format) + 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()) + if isinstance(dif_proof, Sequence): + for proof in dif_proof: pres_ver_result = await verify_presentation( - presentation=dif_proof, - suites=await self._get_all_suites(wallet=wallet), + presentation=proof, + suites=await self._get_all_suites(), document_loader=self._profile.inject(DocumentLoader), challenge=challenge, ) - pres_ex_record.verified = json.dumps(pres_ver_result.verified) - return pres_ex_record + 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_ex_record.verified = json.dumps(pres_ver_result.verified) + return pres_ex_record 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 8143083c0a..8986ff42ac 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 @@ -396,7 +396,7 @@ def test_validate_fields(self): self.handler.validate_fields(PRES_20, incorrect_pres) async def test_get_all_suites(self): - suites = await self.handler._get_all_suites(self.wallet) + suites = await self.handler._get_all_suites() assert len(suites) == 4 types = [ Ed25519Signature2018, diff --git a/aries_cloudagent/vc/ld_proofs/crypto/tests/test_wallet_key_pair.py b/aries_cloudagent/vc/ld_proofs/crypto/tests/test_wallet_key_pair.py index 580ce4254a..d1842d9ac2 100644 --- a/aries_cloudagent/vc/ld_proofs/crypto/tests/test_wallet_key_pair.py +++ b/aries_cloudagent/vc/ld_proofs/crypto/tests/test_wallet_key_pair.py @@ -2,7 +2,8 @@ from aries_cloudagent.wallet.key_type import ED25519 - +from .....core.in_memory import InMemoryProfile +from .....wallet.in_memory import InMemoryWallet from ...error import LinkedDataProofException from ..wallet_key_pair import WalletKeyPair @@ -10,10 +11,10 @@ class TestWalletKeyPair(TestCase): async def setUp(self): - self.wallet = async_mock.MagicMock() + self.profile = InMemoryProfile.test_profile() async def test_sign_x_no_public_key(self): - key_pair = WalletKeyPair(wallet=self.wallet, key_type=ED25519) + key_pair = WalletKeyPair(profile=self.profile, key_type=ED25519) with self.assertRaises(LinkedDataProofException) as context: await key_pair.sign(b"Message") @@ -22,23 +23,26 @@ async def test_sign_x_no_public_key(self): async def test_sign(self): public_key_base58 = "verkey" key_pair = WalletKeyPair( - wallet=self.wallet, + profile=self.profile, key_type=ED25519, public_key_base58=public_key_base58, ) signed = async_mock.MagicMock() - self.wallet.sign_message = async_mock.CoroutineMock(return_value=signed) - - singed_ret = await key_pair.sign(b"Message") + with async_mock.patch.object( + InMemoryWallet, + "sign_message", + async_mock.CoroutineMock(return_value=signed), + ) as sign_message: + singed_ret = await key_pair.sign(b"Message") - assert signed == singed_ret - self.wallet.sign_message.assert_called_once_with( - message=b"Message", from_verkey=public_key_base58 - ) + assert signed == singed_ret + sign_message.assert_called_once_with( + message=b"Message", from_verkey=public_key_base58 + ) async def test_verify_x_no_public_key(self): - key_pair = WalletKeyPair(wallet=self.wallet, key_type=ED25519) + key_pair = WalletKeyPair(profile=self.profile, key_type=ED25519) with self.assertRaises(LinkedDataProofException) as context: await key_pair.verify(b"Message", b"signature") @@ -47,24 +51,28 @@ async def test_verify_x_no_public_key(self): async def test_verify(self): public_key_base58 = "verkey" key_pair = WalletKeyPair( - wallet=self.wallet, + profile=self.profile, key_type=ED25519, public_key_base58=public_key_base58, ) - self.wallet.verify_message = async_mock.CoroutineMock(return_value=True) - verified = await key_pair.verify(b"Message", b"signature") - - assert verified - self.wallet.verify_message.assert_called_once_with( - message=b"Message", - signature=b"signature", - from_verkey=public_key_base58, - key_type=ED25519, - ) + with async_mock.patch.object( + InMemoryWallet, + "verify_message", + async_mock.CoroutineMock(return_value=True), + ) as verify_message: + verified = await key_pair.verify(b"Message", b"signature") + + assert verified + verify_message.assert_called_once_with( + message=b"Message", + signature=b"signature", + from_verkey=public_key_base58, + key_type=ED25519, + ) async def test_from_verification_method_x_no_public_key_base58(self): - key_pair = WalletKeyPair(wallet=self.wallet, key_type=ED25519) + key_pair = WalletKeyPair(profile=self.profile, key_type=ED25519) with self.assertRaises(LinkedDataProofException) as context: key_pair.from_verification_method({}) diff --git a/aries_cloudagent/vc/ld_proofs/crypto/wallet_key_pair.py b/aries_cloudagent/vc/ld_proofs/crypto/wallet_key_pair.py index f666b739ac..8d99eaed90 100644 --- a/aries_cloudagent/vc/ld_proofs/crypto/wallet_key_pair.py +++ b/aries_cloudagent/vc/ld_proofs/crypto/wallet_key_pair.py @@ -2,9 +2,10 @@ from typing import List, Optional, Union -from ....wallet.util import b58_to_bytes -from ....wallet.key_type import KeyType +from ....core.profile import Profile from ....wallet.base import BaseWallet +from ....wallet.key_type import KeyType +from ....wallet.util import b58_to_bytes from ..error import LinkedDataProofException @@ -17,13 +18,13 @@ class WalletKeyPair(KeyPair): def __init__( self, *, - wallet: BaseWallet, + profile: Profile, key_type: KeyType, public_key_base58: Optional[str] = None, ) -> None: """Initialize new WalletKeyPair instance.""" super().__init__() - self.wallet = wallet + self.profile = profile self.key_type = key_type self.public_key_base58 = public_key_base58 @@ -33,10 +34,12 @@ async def sign(self, message: Union[List[bytes], bytes]) -> bytes: raise LinkedDataProofException( "Unable to sign message with WalletKey: No key to sign with" ) - return await self.wallet.sign_message( - message=message, - from_verkey=self.public_key_base58, - ) + async with self.profile.session() as session: + wallet = session.inject(BaseWallet) + return await wallet.sign_message( + message=message, + from_verkey=self.public_key_base58, + ) async def verify( self, message: Union[List[bytes], bytes], signature: bytes @@ -47,12 +50,14 @@ async def verify( "Unable to verify message with key pair: No key to verify with" ) - return await self.wallet.verify_message( - message=message, - signature=signature, - from_verkey=self.public_key_base58, - key_type=self.key_type, - ) + async with self.profile.session() as session: + wallet = session.inject(BaseWallet) + return await wallet.verify_message( + message=message, + signature=signature, + from_verkey=self.public_key_base58, + key_type=self.key_type, + ) def from_verification_method(self, verification_method: dict) -> "WalletKeyPair": """Create new WalletKeyPair from public key in verification method.""" @@ -62,7 +67,7 @@ def from_verification_method(self, verification_method: dict) -> "WalletKeyPair" ) return WalletKeyPair( - wallet=self.wallet, + profile=self.profile, key_type=self.key_type, public_key_base58=verification_method["publicKeyBase58"], ) diff --git a/aries_cloudagent/vc/ld_proofs/suites/tests/test_bbs_bls_signature_2020.py b/aries_cloudagent/vc/ld_proofs/suites/tests/test_bbs_bls_signature_2020.py index ba477604e8..a5a141c174 100644 --- a/aries_cloudagent/vc/ld_proofs/suites/tests/test_bbs_bls_signature_2020.py +++ b/aries_cloudagent/vc/ld_proofs/suites/tests/test_bbs_bls_signature_2020.py @@ -3,9 +3,9 @@ from aries_cloudagent.wallet.key_type import BLS12381G2 +from .....core.in_memory import InMemoryProfile from .....did.did_key import DIDKey from .....wallet.in_memory import InMemoryWallet -from .....core.in_memory import InMemoryProfile from ....tests.document_loader import custom_document_loader from ....tests.data import ( TEST_LD_DOCUMENT, @@ -38,11 +38,11 @@ async def setUp(self): ).key_id self.sign_key_pair = WalletKeyPair( - wallet=self.wallet, + profile=self.profile, key_type=BLS12381G2, public_key_base58=self.key.verkey, ) - self.verify_key_pair = WalletKeyPair(wallet=self.wallet, key_type=BLS12381G2) + self.verify_key_pair = WalletKeyPair(profile=self.profile, key_type=BLS12381G2) async def test_sign_ld_proofs(self): signed = await sign( diff --git a/aries_cloudagent/vc/ld_proofs/suites/tests/test_bbs_bls_signature_proof_2020.py b/aries_cloudagent/vc/ld_proofs/suites/tests/test_bbs_bls_signature_proof_2020.py index df2080480d..ae217d4b63 100644 --- a/aries_cloudagent/vc/ld_proofs/suites/tests/test_bbs_bls_signature_proof_2020.py +++ b/aries_cloudagent/vc/ld_proofs/suites/tests/test_bbs_bls_signature_proof_2020.py @@ -3,9 +3,9 @@ from aries_cloudagent.wallet.key_type import BLS12381G2 +from .....core.in_memory import InMemoryProfile from .....did.did_key import DIDKey from .....wallet.in_memory import InMemoryWallet -from .....core.in_memory import InMemoryProfile from ....tests.document_loader import custom_document_loader from ....tests.data import ( TEST_LD_DOCUMENT_SIGNED_BBS, @@ -46,7 +46,7 @@ async def setUp(self): self.key.verkey, BLS12381G2 ).key_id - self.key_pair = WalletKeyPair(wallet=self.wallet, key_type=BLS12381G2) + self.key_pair = WalletKeyPair(profile=self.profile, key_type=BLS12381G2) async def test_derive_ld_proofs(self): derived = await derive( diff --git a/aries_cloudagent/vc/ld_proofs/suites/tests/test_ed25519_signature_2018.py b/aries_cloudagent/vc/ld_proofs/suites/tests/test_ed25519_signature_2018.py index 20e510df3e..fdae59d396 100644 --- a/aries_cloudagent/vc/ld_proofs/suites/tests/test_ed25519_signature_2018.py +++ b/aries_cloudagent/vc/ld_proofs/suites/tests/test_ed25519_signature_2018.py @@ -37,11 +37,11 @@ async def setUp(self): ).key_id self.sign_key_pair = WalletKeyPair( - wallet=self.wallet, + profile=self.profile, key_type=ED25519, public_key_base58=self.key.verkey, ) - self.verify_key_pair = WalletKeyPair(wallet=self.wallet, key_type=ED25519) + self.verify_key_pair = WalletKeyPair(profile=self.profile, key_type=ED25519) async def test_sign_ld_proofs(self): signed = await sign( diff --git a/aries_cloudagent/vc/ld_proofs/suites/tests/test_ed25519_signature_2020.py b/aries_cloudagent/vc/ld_proofs/suites/tests/test_ed25519_signature_2020.py index 165a32fc21..c527e3aa48 100644 --- a/aries_cloudagent/vc/ld_proofs/suites/tests/test_ed25519_signature_2020.py +++ b/aries_cloudagent/vc/ld_proofs/suites/tests/test_ed25519_signature_2020.py @@ -32,11 +32,11 @@ async def setUp(self): ).key_id self.sign_key_pair = WalletKeyPair( - wallet=self.wallet, + profile=self.profile, key_type=ED25519, public_key_base58=self.key.verkey, ) - self.verify_key_pair = WalletKeyPair(wallet=self.wallet, key_type=ED25519) + self.verify_key_pair = WalletKeyPair(profile=self.profile, key_type=ED25519) async def test_sign_ld_proofs(self): signed = await sign( diff --git a/aries_cloudagent/vc/ld_proofs/tests/test_ld_proofs.py b/aries_cloudagent/vc/ld_proofs/tests/test_ld_proofs.py index b8eaecc379..26f170793e 100644 --- a/aries_cloudagent/vc/ld_proofs/tests/test_ld_proofs.py +++ b/aries_cloudagent/vc/ld_proofs/tests/test_ld_proofs.py @@ -65,7 +65,7 @@ async def test_sign_Ed25519Signature2018(self): suite = Ed25519Signature2018( verification_method=self.ed25519_verification_method, key_pair=WalletKeyPair( - wallet=self.wallet, + profile=self.profile, key_type=ED25519, public_key_base58=self.ed25519_key_info.verkey, ), @@ -87,7 +87,7 @@ async def test_sign_Ed25519Signature2020(self): suite = Ed25519Signature2020( verification_method=self.ed25519_verification_method, key_pair=WalletKeyPair( - wallet=self.wallet, + profile=self.profile, key_type=ED25519, public_key_base58=self.ed25519_key_info.verkey, ), @@ -105,7 +105,7 @@ async def test_sign_Ed25519Signature2020(self): async def test_verify_Ed25519Signature2018(self): # Verification requires lot less input parameters suite = Ed25519Signature2018( - key_pair=WalletKeyPair(wallet=self.wallet, key_type=ED25519), + key_pair=WalletKeyPair(profile=self.profile, key_type=ED25519), ) result = await verify( @@ -120,7 +120,7 @@ async def test_verify_Ed25519Signature2018(self): async def test_verify_Ed25519Signature2020(self): # Verification requires lot less input parameters suite = Ed25519Signature2020( - key_pair=WalletKeyPair(wallet=self.wallet, key_type=ED25519), + key_pair=WalletKeyPair(profile=self.profile, key_type=ED25519), ) result = await verify( @@ -140,7 +140,7 @@ async def test_sign_BbsBlsSignature2020(self): suite = BbsBlsSignature2020( verification_method=self.bls12381g2_verification_method, key_pair=WalletKeyPair( - wallet=self.wallet, + profile=self.profile, key_type=BLS12381G2, public_key_base58=self.bls12381g2_key_info.verkey, ), @@ -169,7 +169,7 @@ async def test_sign_BbsBlsSignature2020(self): async def test_verify_BbsBlsSignature2020(self): # Verification requires lot less input parameters suite = BbsBlsSignature2020( - key_pair=WalletKeyPair(wallet=self.wallet, key_type=BLS12381G2), + key_pair=WalletKeyPair(profile=self.profile, key_type=BLS12381G2), ) result = await verify( @@ -185,7 +185,7 @@ async def test_verify_BbsBlsSignature2020(self): async def test_derive_BbsBlsSignatureProof2020(self): # Verification requires lot less input parameters suite = BbsBlsSignatureProof2020( - key_pair=WalletKeyPair(wallet=self.wallet, key_type=BLS12381G2), + key_pair=WalletKeyPair(profile=self.profile, key_type=BLS12381G2), ) result = await derive( @@ -201,7 +201,7 @@ async def test_derive_BbsBlsSignatureProof2020(self): async def test_verify_BbsBlsSignatureProof2020(self): # Verification requires lot less input parameters suite = BbsBlsSignatureProof2020( - key_pair=WalletKeyPair(wallet=self.wallet, key_type=BLS12381G2), + key_pair=WalletKeyPair(profile=self.profile, key_type=BLS12381G2), ) result = await verify( diff --git a/aries_cloudagent/vc/tests/test_bbs_mattr_interop.py b/aries_cloudagent/vc/tests/test_bbs_mattr_interop.py index d9f4dde009..0fd50ccbc1 100644 --- a/aries_cloudagent/vc/tests/test_bbs_mattr_interop.py +++ b/aries_cloudagent/vc/tests/test_bbs_mattr_interop.py @@ -51,17 +51,17 @@ async def setUp(self): self.signature_issuer_suite = BbsBlsSignature2020( verification_method="did:example:489398593#test", key_pair=WalletKeyPair( - wallet=self.wallet, + profile=self.profile, key_type=BLS12381G2, public_key_base58=public_key_base58, ), ) self.signature_suite = BbsBlsSignature2020( - key_pair=WalletKeyPair(wallet=self.wallet, key_type=BLS12381G2), + key_pair=WalletKeyPair(profile=self.profile, key_type=BLS12381G2), ) self.proof_suite = BbsBlsSignatureProof2020( - key_pair=WalletKeyPair(wallet=self.wallet, key_type=BLS12381G2) + key_pair=WalletKeyPair(profile=self.profile, key_type=BLS12381G2) ) async def test_sign_bbs_vc_mattr(self): diff --git a/aries_cloudagent/vc/vc_ld/tests/test_vc_ld.py b/aries_cloudagent/vc/vc_ld/tests/test_vc_ld.py index d155dadd8f..d169a1907a 100644 --- a/aries_cloudagent/vc/vc_ld/tests/test_vc_ld.py +++ b/aries_cloudagent/vc/vc_ld/tests/test_vc_ld.py @@ -68,7 +68,7 @@ async def test_issue_Ed25519Signature2018(self): suite = Ed25519Signature2018( verification_method=self.ed25519_verification_method, key_pair=WalletKeyPair( - wallet=self.wallet, + profile=self.profile, key_type=ED25519, public_key_base58=self.ed25519_key_info.verkey, ), @@ -90,7 +90,7 @@ async def test_issue_Ed25519Signature2020(self): suite = Ed25519Signature2020( verification_method=self.ed25519_verification_method, key_pair=WalletKeyPair( - wallet=self.wallet, + profile=self.profile, key_type=ED25519, public_key_base58=self.ed25519_key_info.verkey, ), @@ -133,7 +133,7 @@ async def test_derive_x_invalid_credential_structure(self): async def test_verify_Ed25519Signature2018(self): # Verification requires lot less input parameters suite = Ed25519Signature2018( - key_pair=WalletKeyPair(wallet=self.wallet, key_type=ED25519), + key_pair=WalletKeyPair(profile=self.profile, key_type=ED25519), ) verified = await verify_credential( credential=CREDENTIAL_ISSUED, @@ -146,7 +146,7 @@ async def test_verify_Ed25519Signature2018(self): async def test_verify_Ed25519Signature2020(self): # Verification requires lot less input parameters suite = Ed25519Signature2020( - key_pair=WalletKeyPair(wallet=self.wallet, key_type=ED25519), + key_pair=WalletKeyPair(profile=self.profile, key_type=ED25519), ) verified = await verify_credential( credential=CREDENTIAL_ISSUED_2020, @@ -177,7 +177,7 @@ async def test_issue_BbsBlsSignature2020(self): suite = BbsBlsSignature2020( verification_method=self.bls12381g2_verification_method, key_pair=WalletKeyPair( - wallet=self.wallet, + profile=self.profile, key_type=BLS12381G2, public_key_base58=self.bls12381g2_key_info.verkey, ), @@ -201,7 +201,7 @@ async def test_issue_BbsBlsSignature2020(self): async def test_verify_BbsBlsSignature2020(self): # Verification requires lot less input parameters suite = BbsBlsSignature2020( - key_pair=WalletKeyPair(wallet=self.wallet, key_type=BLS12381G2), + key_pair=WalletKeyPair(profile=self.profile, key_type=BLS12381G2), ) result = await verify_credential( credential=CREDENTIAL_ISSUED_BBS, @@ -227,7 +227,7 @@ async def test_create_presentation_x_invalid_credential_structures(self): suite = Ed25519Signature2018( verification_method=self.ed25519_verification_method, key_pair=WalletKeyPair( - wallet=self.wallet, + profile=self.profile, key_type=ED25519, public_key_base58=self.ed25519_key_info.verkey, ), @@ -260,7 +260,7 @@ async def test_sign_presentation_bbsbls(self): suite = BbsBlsSignature2020( verification_method=self.bls12381g2_verification_method, key_pair=WalletKeyPair( - wallet=self.wallet, + profile=self.profile, key_type=BLS12381G2, public_key_base58=self.bls12381g2_key_info.verkey, ), @@ -269,7 +269,7 @@ async def test_sign_presentation_bbsbls(self): assert unsigned_presentation == PRESENTATION_UNSIGNED unsigned_presentation["@context"].append("https://w3id.org/security/bbs/v1") - presentation = await sign_presentation( + _ = await sign_presentation( presentation=unsigned_presentation, suite=suite, document_loader=custom_document_loader, @@ -278,7 +278,7 @@ async def test_sign_presentation_bbsbls(self): async def test_verify_presentation(self): suite = Ed25519Signature2018( - key_pair=WalletKeyPair(wallet=self.wallet, key_type=ED25519), + key_pair=WalletKeyPair(profile=self.profile, key_type=ED25519), ) verification_result = await verify_presentation( presentation=PRESENTATION_SIGNED, From 59d93fe1dd1f6f984ae34fefe77909699dc48c24 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Oct 2023 23:30:27 +0000 Subject: [PATCH 84/85] chore(deps): Bump urllib3 in /demo/playground/scripts Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.0.2 to 2.0.6. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/2.0.2...2.0.6) --- updated-dependencies: - dependency-name: urllib3 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- demo/playground/scripts/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/playground/scripts/requirements.txt b/demo/playground/scripts/requirements.txt index e15d11d275..20232d456d 100644 --- a/demo/playground/scripts/requirements.txt +++ b/demo/playground/scripts/requirements.txt @@ -20,5 +20,5 @@ requests==2.31.0; python_version >= "3.7" routes==2.5.1 six==1.16.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" tomli==2.0.1; python_version < "3.11" and python_version >= "3.7" -urllib3==2.0.2; python_version >= "3.7" +urllib3==2.0.6; python_version >= "3.7" yarl==1.9.2; python_version >= "3.7" From 0d77e87fd3638b5ed33d0363df921cd08d37581a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Oct 2023 00:22:35 +0000 Subject: [PATCH 85/85] chore(deps): Bump urllib3 from 2.0.5 to 2.0.6 Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.0.5 to 2.0.6. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/v2.0.5...2.0.6) --- updated-dependencies: - dependency-name: urllib3 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index ebd2184a66..6527209fee 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2603,13 +2603,13 @@ files = [ [[package]] name = "urllib3" -version = "2.0.5" +version = "2.0.6" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.7" files = [ - {file = "urllib3-2.0.5-py3-none-any.whl", hash = "sha256:ef16afa8ba34a1f989db38e1dbbe0c302e4289a47856990d0682e374563ce35e"}, - {file = "urllib3-2.0.5.tar.gz", hash = "sha256:13abf37382ea2ce6fb744d4dad67838eec857c9f4f57009891805e0b5e123594"}, + {file = "urllib3-2.0.6-py3-none-any.whl", hash = "sha256:7a7c7003b000adf9e7ca2a377c9688bbc54ed41b985789ed576570342a375cd2"}, + {file = "urllib3-2.0.6.tar.gz", hash = "sha256:b19e1a85d206b56d7df1d5e683df4a7725252a964e3993648dd0fb5a1c157564"}, ] [package.extras]