Skip to content

Commit f5c49b0

Browse files
authored
did:tdw resolver (openwallet-foundation#3237)
* did:tdw resolver Signed-off-by: jamshale <[email protected]> * Update unit tests Signed-off-by: jamshale <[email protected]> * Update poetry.lock Signed-off-by: jamshale <[email protected]> --------- Signed-off-by: jamshale <[email protected]>
1 parent c18f6d1 commit f5c49b0

File tree

8 files changed

+246
-5
lines changed

8 files changed

+246
-5
lines changed

acapy_agent/messaging/tests/test_valid.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
CREDENTIAL_TYPE_VALIDATE,
1313
DID_KEY_VALIDATE,
1414
DID_POSTURE_VALIDATE,
15+
DID_TDW_VALIDATE,
1516
ENDPOINT_TYPE_VALIDATE,
1617
ENDPOINT_VALIDATE,
1718
INDY_CRED_DEF_ID_VALIDATE,
@@ -114,6 +115,24 @@ def test_indy_did(self):
114115
INDY_DID_VALIDATE("Q4zqM7aXqm7gDQkUVLng9h")
115116
INDY_DID_VALIDATE("did:sov:Q4zqM7aXqm7gDQkUVLng9h")
116117

118+
def test_tdw_did(self):
119+
valid_tdw_dids = [
120+
"did:tdw:QmUchSB5f5DJQks9CeyLJjhAy4iKJcYzRyiuYq3sjV13px:example.com",
121+
"did:tdw:QmZiKXwQVfyZVuvCsuHpQh4arSUpEmeVVRvSfv3uiEycSr:example.com%3A5000",
122+
]
123+
for valid_tdw_did in valid_tdw_dids:
124+
DID_TDW_VALIDATE(valid_tdw_did)
125+
126+
non_valid_tdw_dids = [
127+
"did:web:QmUchSB5f5DJQks9CeyLJjhAy4iKJcYzRyiuYq3sjV13px",
128+
# Did urls are not allowed
129+
"did:tdw:QmP9VWaTCHcyztDpRj9XSHvZbmYe3m9HZ61KoDtZgWaXVU:example.com%3A5000#z6MkkzY9skorPaoEbCJFKUo7thD8Yb8MBs28aJRopf1TUo9V",
130+
"did:tdw:QmZiKXwQVfyZVuvCsuHpQh4arSUpEmeVVRvSfv3uiEycSr:example.com%3A5000/whois",
131+
]
132+
for non_valid_tdw_did in non_valid_tdw_dids:
133+
with self.assertRaises(ValidationError):
134+
DID_TDW_VALIDATE(non_valid_tdw_did)
135+
117136
def test_indy_raw_public_key(self):
118137
non_indy_raw_public_keys = [
119138
"Q4zqM7aXqm7gDQkUVLng9JQ4zqM7aXqm7gDQkUVLng9I", # 'I' not a base58 char

acapy_agent/messaging/valid.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,20 @@ def __init__(self):
319319
)
320320

321321

322+
class DIDTdw(Regexp):
323+
"""Validate value against did:tdw specification."""
324+
325+
EXAMPLE = "did:tdw:QmP9VWaTCHcyztDpRj9XSHvZbmYe3m9HZ61KoDtZgWaXVU:example.com%3A5000"
326+
PATTERN = re.compile(r"^(did:tdw:)([a-zA-Z0-9%._-]*:)*[a-zA-Z0-9%._-]+$")
327+
328+
def __init__(self):
329+
"""Initialize the instance."""
330+
331+
super().__init__(
332+
DIDTdw.PATTERN, error="Value {input} is not in W3C did:tdw format"
333+
)
334+
335+
322336
class DIDPosture(OneOf):
323337
"""Validate value against defined DID postures."""
324338

@@ -934,6 +948,9 @@ def __init__(
934948
DID_WEB_VALIDATE = DIDWeb()
935949
DID_WEB_EXAMPLE = DIDWeb.EXAMPLE
936950

951+
DID_TDW_VALIDATE = DIDTdw()
952+
DID_TDW_EXAMPLE = DIDTdw.EXAMPLE
953+
937954
ROUTING_KEY_VALIDATE = RoutingKey()
938955
ROUTING_KEY_EXAMPLE = RoutingKey.EXAMPLE
939956

acapy_agent/resolver/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,12 @@ async def setup(context: InjectionContext):
4949
await web_resolver.setup(context)
5050
registry.register_resolver(web_resolver)
5151

52+
tdw_resolver = ClassProvider(
53+
"acapy_agent.resolver.default.tdw.TdwDIDResolver"
54+
).provide(context.settings, context.injector)
55+
await tdw_resolver.setup(context)
56+
registry.register_resolver(tdw_resolver)
57+
5258
if context.settings.get("resolver.universal"):
5359
universal_resolver = ClassProvider(
5460
"acapy_agent.resolver.default.universal.UniversalResolver"
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
"""TDW DID Resolver.
2+
3+
Resolution is performed by the did_tdw library.
4+
"""
5+
6+
from re import Pattern
7+
from typing import Optional, Sequence, Text
8+
9+
from did_tdw.resolver import ResolutionResult, resolve_did
10+
11+
from ...config.injection_context import InjectionContext
12+
from ...core.profile import Profile
13+
from ...messaging.valid import DIDTdw
14+
from ..base import BaseDIDResolver, ResolverType
15+
16+
17+
class TdwDIDResolver(BaseDIDResolver):
18+
"""TDW DID Resolver."""
19+
20+
def __init__(self):
21+
"""Initialize the TDW DID Resolver."""
22+
super().__init__(ResolverType.NATIVE)
23+
24+
async def setup(self, context: InjectionContext):
25+
"""Perform required setup for TDW DID resolution."""
26+
27+
@property
28+
def supported_did_regex(self) -> Pattern:
29+
"""Return supported DID regex of TDW DID Resolver."""
30+
return DIDTdw.PATTERN
31+
32+
async def _resolve(
33+
self, profile: Profile, did: str, service_accept: Optional[Sequence[Text]] = None
34+
) -> dict:
35+
"""Resolve DID using TDW."""
36+
response: ResolutionResult = await resolve_did(did)
37+
if response.resolution_metadata and response.resolution_metadata.get("error"):
38+
return response.resolution_metadata
39+
40+
return response.document
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import pytest
2+
3+
from ....core.profile import Profile
4+
from ....messaging.valid import DIDTdw
5+
from ....utils.testing import create_test_profile
6+
from ..tdw import TdwDIDResolver
7+
8+
TEST_DID = "did:tdw:Qma6mc1qZw3NqxwX6SB5GPQYzP4pGN2nXD15Jwi4bcDBKu:domain.example"
9+
10+
11+
@pytest.fixture
12+
def resolver():
13+
"""Resolver fixture."""
14+
yield TdwDIDResolver()
15+
16+
17+
@pytest.fixture
18+
async def profile():
19+
"""Profile fixture."""
20+
yield await create_test_profile()
21+
22+
23+
@pytest.mark.asyncio
24+
async def test_supported_did_regex(profile, resolver: TdwDIDResolver):
25+
"""Test the supported_did_regex."""
26+
assert resolver.supported_did_regex == DIDTdw.PATTERN
27+
assert await resolver.supports(
28+
profile,
29+
TEST_DID,
30+
)
31+
32+
33+
@pytest.mark.asyncio
34+
async def test_resolve(resolver: TdwDIDResolver, profile: Profile):
35+
"""Test resolve method."""
36+
assert await resolver.resolve(profile, TEST_DID)

acapy_agent/wallet/did_method.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,13 @@ def holder_defined_did(self) -> HolderDefinedDid:
9090
holder_defined_did=HolderDefinedDid.NO,
9191
)
9292

93+
TDW = DIDMethod(
94+
name="tdw",
95+
key_types=[ED25519, X25519],
96+
rotation=False,
97+
holder_defined_did=HolderDefinedDid.NO,
98+
)
99+
93100

94101
class DIDMethods:
95102
"""DID Method class specifying DID methods with supported key types."""
@@ -102,6 +109,7 @@ def __init__(self) -> None:
102109
WEB.method_name: WEB,
103110
PEER2.method_name: PEER2,
104111
PEER4.method_name: PEER4,
112+
TDW.method_name: TDW,
105113
}
106114

107115
def registered(self, method: str) -> bool:

poetry.lock

Lines changed: 115 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,13 @@ requests = "~2.32.3"
4545
rlp = "4.0.1"
4646
unflatten = "~0.2"
4747
sd-jwt = "^0.10.3"
48+
uuid_utils = "^0.9.0"
49+
50+
# did libraries
4851
did-peer-2 = "^0.1.2"
4952
did-peer-4 = "^0.1.4"
50-
uuid_utils = "^0.9.0"
53+
did-tdw = "^0.2.1"
54+
5155

5256
# askar
5357
aries-askar = { version = "~0.3.2", optional = true }

0 commit comments

Comments
 (0)