Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

did:tdw resolver #3237

Merged
merged 3 commits into from
Nov 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions acapy_agent/messaging/tests/test_valid.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
CREDENTIAL_TYPE_VALIDATE,
DID_KEY_VALIDATE,
DID_POSTURE_VALIDATE,
DID_TDW_VALIDATE,
ENDPOINT_TYPE_VALIDATE,
ENDPOINT_VALIDATE,
INDY_CRED_DEF_ID_VALIDATE,
Expand Down Expand Up @@ -114,6 +115,24 @@ def test_indy_did(self):
INDY_DID_VALIDATE("Q4zqM7aXqm7gDQkUVLng9h")
INDY_DID_VALIDATE("did:sov:Q4zqM7aXqm7gDQkUVLng9h")

def test_tdw_did(self):
valid_tdw_dids = [
"did:tdw:QmUchSB5f5DJQks9CeyLJjhAy4iKJcYzRyiuYq3sjV13px:example.com",
"did:tdw:QmZiKXwQVfyZVuvCsuHpQh4arSUpEmeVVRvSfv3uiEycSr:example.com%3A5000",
]
for valid_tdw_did in valid_tdw_dids:
DID_TDW_VALIDATE(valid_tdw_did)

non_valid_tdw_dids = [
"did:web:QmUchSB5f5DJQks9CeyLJjhAy4iKJcYzRyiuYq3sjV13px",
# Did urls are not allowed
"did:tdw:QmP9VWaTCHcyztDpRj9XSHvZbmYe3m9HZ61KoDtZgWaXVU:example.com%3A5000#z6MkkzY9skorPaoEbCJFKUo7thD8Yb8MBs28aJRopf1TUo9V",
"did:tdw:QmZiKXwQVfyZVuvCsuHpQh4arSUpEmeVVRvSfv3uiEycSr:example.com%3A5000/whois",
]
for non_valid_tdw_did in non_valid_tdw_dids:
with self.assertRaises(ValidationError):
DID_TDW_VALIDATE(non_valid_tdw_did)

def test_indy_raw_public_key(self):
non_indy_raw_public_keys = [
"Q4zqM7aXqm7gDQkUVLng9JQ4zqM7aXqm7gDQkUVLng9I", # 'I' not a base58 char
Expand Down
17 changes: 17 additions & 0 deletions acapy_agent/messaging/valid.py
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,20 @@ def __init__(self):
)


class DIDTdw(Regexp):
"""Validate value against did:tdw specification."""

EXAMPLE = "did:tdw:QmP9VWaTCHcyztDpRj9XSHvZbmYe3m9HZ61KoDtZgWaXVU:example.com%3A5000"
PATTERN = re.compile(r"^(did:tdw:)([a-zA-Z0-9%._-]*:)*[a-zA-Z0-9%._-]+$")

def __init__(self):
"""Initialize the instance."""

super().__init__(
DIDTdw.PATTERN, error="Value {input} is not in W3C did:tdw format"
)


class DIDPosture(OneOf):
"""Validate value against defined DID postures."""

Expand Down Expand Up @@ -934,6 +948,9 @@ def __init__(
DID_WEB_VALIDATE = DIDWeb()
DID_WEB_EXAMPLE = DIDWeb.EXAMPLE

DID_TDW_VALIDATE = DIDTdw()
DID_TDW_EXAMPLE = DIDTdw.EXAMPLE

ROUTING_KEY_VALIDATE = RoutingKey()
ROUTING_KEY_EXAMPLE = RoutingKey.EXAMPLE

Expand Down
6 changes: 6 additions & 0 deletions acapy_agent/resolver/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ async def setup(context: InjectionContext):
await web_resolver.setup(context)
registry.register_resolver(web_resolver)

tdw_resolver = ClassProvider(
"acapy_agent.resolver.default.tdw.TdwDIDResolver"
).provide(context.settings, context.injector)
await tdw_resolver.setup(context)
registry.register_resolver(tdw_resolver)

if context.settings.get("resolver.universal"):
universal_resolver = ClassProvider(
"acapy_agent.resolver.default.universal.UniversalResolver"
Expand Down
40 changes: 40 additions & 0 deletions acapy_agent/resolver/default/tdw.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"""TDW DID Resolver.

Resolution is performed by the did_tdw library.
"""

from re import Pattern
from typing import Optional, Sequence, Text

from did_tdw.resolver import ResolutionResult, resolve_did

from ...config.injection_context import InjectionContext
from ...core.profile import Profile
from ...messaging.valid import DIDTdw
from ..base import BaseDIDResolver, ResolverType


class TdwDIDResolver(BaseDIDResolver):
"""TDW DID Resolver."""

def __init__(self):
"""Initialize the TDW DID Resolver."""
super().__init__(ResolverType.NATIVE)

async def setup(self, context: InjectionContext):
"""Perform required setup for TDW DID resolution."""

@property
def supported_did_regex(self) -> Pattern:
"""Return supported DID regex of TDW DID Resolver."""
return DIDTdw.PATTERN

async def _resolve(
self, profile: Profile, did: str, service_accept: Optional[Sequence[Text]] = None
) -> dict:
"""Resolve DID using TDW."""
response: ResolutionResult = await resolve_did(did)
if response.resolution_metadata and response.resolution_metadata.get("error"):
return response.resolution_metadata

return response.document
36 changes: 36 additions & 0 deletions acapy_agent/resolver/default/tests/test_tdw.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import pytest

from ....core.profile import Profile
from ....messaging.valid import DIDTdw
from ....utils.testing import create_test_profile
from ..tdw import TdwDIDResolver

TEST_DID = "did:tdw:Qma6mc1qZw3NqxwX6SB5GPQYzP4pGN2nXD15Jwi4bcDBKu:domain.example"


@pytest.fixture
def resolver():
"""Resolver fixture."""
yield TdwDIDResolver()


@pytest.fixture
async def profile():
"""Profile fixture."""
yield await create_test_profile()


@pytest.mark.asyncio
async def test_supported_did_regex(profile, resolver: TdwDIDResolver):
"""Test the supported_did_regex."""
assert resolver.supported_did_regex == DIDTdw.PATTERN
assert await resolver.supports(
profile,
TEST_DID,
)


@pytest.mark.asyncio
async def test_resolve(resolver: TdwDIDResolver, profile: Profile):
"""Test resolve method."""
assert await resolver.resolve(profile, TEST_DID)
8 changes: 8 additions & 0 deletions acapy_agent/wallet/did_method.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,13 @@ def holder_defined_did(self) -> HolderDefinedDid:
holder_defined_did=HolderDefinedDid.NO,
)

TDW = DIDMethod(
name="tdw",
key_types=[ED25519, X25519],
rotation=False,
holder_defined_did=HolderDefinedDid.NO,
)


class DIDMethods:
"""DID Method class specifying DID methods with supported key types."""
Expand All @@ -102,6 +109,7 @@ def __init__(self) -> None:
WEB.method_name: WEB,
PEER2.method_name: PEER2,
PEER4.method_name: PEER4,
TDW.method_name: TDW,
}

def registered(self, method: str) -> bool:
Expand Down
119 changes: 115 additions & 4 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,13 @@ requests = "~2.32.3"
rlp = "4.0.1"
unflatten = "~0.2"
sd-jwt = "^0.10.3"
uuid_utils = "^0.9.0"

# did libraries
did-peer-2 = "^0.1.2"
did-peer-4 = "^0.1.4"
uuid_utils = "^0.9.0"
did-tdw = "^0.2.1"


# askar
aries-askar = { version = "~0.3.2", optional = true }
Expand Down
Loading