Skip to content

Commit 7d04dd0

Browse files
Align HCS DID Events with Universal Resolver driver (#33)
Signed-off-by: Alexander Shenshin <[email protected]>
1 parent 901dd66 commit 7d04dd0

File tree

13 files changed

+81
-88
lines changed

13 files changed

+81
-88
lines changed

hiero_did_sdk_python/did/did_document.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import logging
2+
from datetime import datetime
23
from typing import cast
34

45
from hiero_sdk_python import PublicKey
@@ -29,7 +30,6 @@ class DidDocument(Serializable):
2930
Attributes:
3031
created: Creation timestamp
3132
updated: Last update timestamp
32-
version_id: DID document version ID (equals to last update timestamp)
3333
deactivated: DID document deactivation status
3434
controller: Dictionary representing DID document controller info
3535
services: DID document services dictionary
@@ -42,9 +42,8 @@ def __init__(self, id_: str):
4242
self.id_ = id_
4343
self.context = DID_DOCUMENT_CONTEXT
4444

45-
self.created: float | None = None
46-
self.updated: float | None = None
47-
self.version_id: str | None = None
45+
self.created: datetime | None = None
46+
self.updated: datetime | None = None
4847
self.deactivated: bool = False
4948

5049
self.controller: dict | None = None
@@ -61,6 +60,10 @@ def __init__(self, id_: str):
6160

6261
self._public_key: PublicKey | None = None
6362

63+
@property
64+
def version_timestamp(self) -> datetime | None:
65+
return self.updated or self.created
66+
6467
async def process_messages(self, envelopes: list[HcsDidMessageEnvelope]):
6568
"""
6669
Process HCS DID messages - apply DID document state changes according to events.
@@ -202,7 +205,7 @@ async def _process_create_message(self, message: HcsDidMessage): # noqa: C901
202205

203206
did_owner_event = cast(HcsDidUpdateDidOwnerEvent, event)
204207

205-
self.controller = did_owner_event.get_owner_def()
208+
self.controller = did_owner_event.get_controller_verification_method()
206209
self._public_key = did_owner_event.public_key
207210
self._on_activated(message.timestamp)
208211
case HcsDidEventTarget.SERVICE:
@@ -259,7 +262,7 @@ def _process_update_message(self, message: HcsDidMessage):
259262
case HcsDidEventTarget.DID_OWNER:
260263
did_owner_event = cast(HcsDidUpdateDidOwnerEvent, event)
261264

262-
self.controller = did_owner_event.get_owner_def()
265+
self.controller = did_owner_event.get_controller_verification_method()
263266
self._public_key = did_owner_event.public_key
264267
self._on_updated(message.timestamp)
265268
case HcsDidEventTarget.SERVICE:
@@ -412,18 +415,15 @@ def _is_message_signature_valid(self, message: HcsDidMessage, signature: str) ->
412415

413416
return True
414417

415-
def _on_activated(self, timestamp: float):
418+
def _on_activated(self, timestamp: datetime):
416419
self.created = timestamp
417420
self.updated = timestamp
418421
self.deactivated = False
419-
self.version_id = str(timestamp)
420422

421-
def _on_updated(self, timestamp: float):
423+
def _on_updated(self, timestamp: datetime):
422424
self.updated = timestamp
423-
self.version_id = str(timestamp)
424425

425426
def _on_deactivated(self):
426427
self.created = None
427428
self.updated = None
428429
self.deactivated = True
429-
self.version_id = None

hiero_did_sdk_python/did/hcs/events/owner/hcs_did_update_did_owner_event.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
from .....did.types import SupportedKeyType
77
from .....utils.encoding import b58_to_bytes, bytes_to_b58
8-
from ....utils import is_owner_event_id_valid
8+
from ....utils import is_valid_did
99
from ..hcs_did_event import HcsDidEvent
1010
from ..hcs_did_event_target import HcsDidEventTarget
1111

@@ -19,8 +19,8 @@ class HcsDidUpdateDidOwnerEvent(HcsDidEvent):
1919
event_target: ClassVar[HcsDidEventTarget] = HcsDidEventTarget.DID_OWNER
2020

2121
def __post_init__(self):
22-
if not is_owner_event_id_valid(self.id_):
23-
raise Exception("Event ID is invalid. Expected format: {did}#did-root-key")
22+
if not is_valid_did(self.id_):
23+
raise Exception("Event ID is invalid. Expected Hedera DID format")
2424

2525
def get_owner_def(self):
2626
return {
@@ -30,6 +30,13 @@ def get_owner_def(self):
3030
"publicKeyBase58": bytes_to_b58(self.public_key.to_bytes_raw()),
3131
}
3232

33+
def get_controller_verification_method(self):
34+
owner_def = self.get_owner_def()
35+
return {
36+
**owner_def,
37+
"id": f"{owner_def["id"]}#did-root-key",
38+
}
39+
3340
@classmethod
3441
def from_json_payload(cls, payload: dict):
3542
event_json = payload[cls.event_target]

hiero_did_sdk_python/did/hcs/hcs_did_message.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import json
2-
import time
2+
from datetime import UTC, datetime
33

44
from ...hcs import HcsMessage, HcsMessageEnvelope
55
from ...utils.encoding import b64_to_str, str_to_b64
@@ -61,11 +61,13 @@ def _parse_hcs_did_event(event_base64: str, operation: DidDocumentOperation) ->
6161

6262

6363
class HcsDidMessage(HcsMessage):
64-
def __init__(self, operation: DidDocumentOperation, did: str, event: HcsDidEvent, timestamp: float = time.time()):
64+
def __init__(
65+
self, operation: DidDocumentOperation, did: str, event: HcsDidEvent, timestamp: datetime | None = None
66+
):
6567
self.operation = operation
6668
self.did = did
6769
self.event = event
68-
self.timestamp = timestamp
70+
self.timestamp = timestamp or datetime.now(UTC)
6971

7072
@property
7173
def event_base64(self):
@@ -91,13 +93,18 @@ def from_json_payload(cls, payload: dict):
9193
match payload:
9294
case {"timestamp": timestamp, "operation": operation, "did": did, "event": event_base64}:
9395
parsed_event = _parse_hcs_did_event(event_base64, operation)
94-
return cls(operation=DidDocumentOperation(operation), did=did, event=parsed_event, timestamp=timestamp)
96+
return cls(
97+
operation=DidDocumentOperation(operation),
98+
did=did,
99+
event=parsed_event,
100+
timestamp=datetime.fromisoformat(timestamp),
101+
)
95102
case _:
96103
raise Exception(f"{cls.__name__} JSON parsing failed: Invalid JSON structure")
97104

98105
def get_json_payload(self):
99106
return {
100-
"timestamp": self.timestamp,
107+
"timestamp": self.timestamp.isoformat().replace("+00:00", "Z"),
101108
"operation": self.operation.value,
102109
"did": self.did,
103110
"event": self.event_base64,

hiero_did_sdk_python/did/hedera_did.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ async def register(self):
8888
)
8989

9090
hcs_event = HcsDidUpdateDidOwnerEvent(
91-
id_=f"{self.identifier}#did-root-key",
91+
id_=self.identifier,
9292
controller=self.identifier,
9393
public_key=self._private_key.public_key(),
9494
type_=self._key_type,
@@ -124,7 +124,7 @@ async def change_owner(self, controller: str, new_private_key_der: str):
124124
self._key_type = new_key_type
125125

126126
hcs_event = HcsDidUpdateDidOwnerEvent(
127-
id_=f"{self.identifier}#did-root-key",
127+
id_=cast(str, self.identifier),
128128
controller=controller,
129129
public_key=self._private_key.public_key(),
130130
type_=self._key_type,

hiero_did_sdk_python/did/hedera_did_resolver.py

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import datetime
21
import time
32
from enum import StrEnum
43
from typing import cast
@@ -106,7 +105,12 @@ async def resolve(self, did: str) -> DIDResolutionResult:
106105

107106
self._cache.set(
108107
topic_id,
109-
TimestampedRecord(did_document, did_document.updated or did_document.created or time.time()),
108+
TimestampedRecord(
109+
did_document,
110+
did_document.version_timestamp.timestamp()
111+
if did_document.version_timestamp
112+
else time.time(),
113+
),
110114
)
111115
else:
112116
registered_did = HederaDid(identifier=did, client=self._client)
@@ -115,27 +119,26 @@ async def resolve(self, did: str) -> DIDResolutionResult:
115119

116120
self._cache.set(
117121
topic_id,
118-
TimestampedRecord(did_document, did_document.updated or did_document.created or time.time()),
122+
TimestampedRecord(
123+
did_document,
124+
did_document.version_timestamp.timestamp() if did_document.version_timestamp else time.time(),
125+
),
119126
)
120127

121-
document_meta = {
122-
"versionId": did_document.version_id,
123-
}
128+
document_meta: dict = {"deactivated": did_document.deactivated}
124129

125130
if not did_document.deactivated:
126131
document_meta.update({
127-
"created": datetime.date.fromtimestamp(cast(float, did_document.created)).isoformat()
132+
"created": did_document.created.isoformat().replace("+00:00", "Z")
128133
if did_document.created
129134
else None,
130-
"updated": datetime.date.fromtimestamp(cast(float, did_document.updated)).isoformat()
135+
"updated": did_document.updated.isoformat().replace("+00:00", "Z")
131136
if did_document.updated
132137
else None,
133138
})
134139

135-
status = {"deactivated": True} if did_document.deactivated else {}
136-
137140
return {
138-
"didDocumentMetadata": cast(DIDDocumentMetadata, {**status, **document_meta}),
141+
"didDocumentMetadata": cast(DIDDocumentMetadata, document_meta),
139142
"didResolutionMetadata": {"contentType": "application/did+ld+json"},
140143
"didDocument": cast(DIDDocument, did_document.get_json_payload()),
141144
}

hiero_did_sdk_python/did/utils.py

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,15 @@ def is_valid_did(did: str) -> bool:
2424
if not identifier:
2525
return False
2626

27-
parse_identifier(identifier)
28-
return True
29-
30-
31-
def is_owner_event_id_valid(event_id: str) -> bool:
32-
identifier, id_ = event_id.split("#") if "#" in event_id else [event_id, ""]
33-
34-
if not identifier or not id_:
27+
try:
28+
parse_identifier(identifier)
29+
return True
30+
except Exception:
3531
return False
3632

37-
parse_identifier(identifier)
3833

39-
return bool(OWNER_KEY_POSTFIX_REGEX.match(id_))
34+
def is_owner_event_id_valid(event_id: str) -> bool:
35+
return is_valid_did(event_id)
4036

4137

4238
def is_service_event_id_valid(event_id: str) -> bool:
@@ -45,9 +41,11 @@ def is_service_event_id_valid(event_id: str) -> bool:
4541
if not identifier or not id_:
4642
return False
4743

48-
parse_identifier(identifier)
49-
50-
return bool(SERVICE_ID_POSTFIX_REGEX.match(id_))
44+
try:
45+
parse_identifier(identifier)
46+
return bool(SERVICE_ID_POSTFIX_REGEX.match(id_))
47+
except Exception:
48+
return False
5149

5250

5351
def is_key_event_id_valid(event_id: str) -> bool:
@@ -56,9 +54,11 @@ def is_key_event_id_valid(event_id: str) -> bool:
5654
if not identifier or not id_:
5755
return False
5856

59-
parse_identifier(identifier)
60-
61-
return bool(KEY_ID_POSTFIX_REGEX.match(id_))
57+
try:
58+
parse_identifier(identifier)
59+
return bool(KEY_ID_POSTFIX_REGEX.match(id_))
60+
except Exception:
61+
return False
6262

6363

6464
@dataclass

hiero_did_sdk_python/utils/serializable.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ def to_json(self) -> str:
4343
A JSON representation of this message
4444
4545
"""
46-
return json.dumps(self.get_json_payload())
46+
return json.dumps(self.get_json_payload(), separators=(",", ":"))
4747

4848
@abstractmethod
4949
def get_json_payload(self) -> dict:

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "hiero-did-sdk-python"
3-
version = "0.1.1"
3+
version = "0.1.2"
44
description = "The repository contains the Python SDK for managing DID Documents and Anoncreds Verifiable Credentials registry using Hedera Consensus Service."
55
authors = ["Alexander Shenshin <[email protected]>", "Paulo Caldas <[email protected]>"]
66
repository = "https://github.com/hiero-ledger/hiero-did-sdk-python"

tests/integration/test_hedera_did.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ async def test_recreates_deleted_did_with_same_topic(self, client: Client):
102102
await asyncio.sleep(5)
103103

104104
did_topic_messages = await resolve_did_topic_messages(did.topic_id, client)
105-
assert len(did_topic_messages) == 2
105+
assert len(did_topic_messages) == 3
106106

107107
class TestResolve:
108108
async def test_throws_if_not_registered(self, client: Client):

tests/integration/test_hedera_did_resolver.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ async def test_returns_success_response(self, client: Client, Something):
8989
"didDocumentMetadata": {
9090
"created": Something,
9191
"updated": Something,
92-
"versionId": Something,
92+
"deactivated": False,
9393
},
9494
"didResolutionMetadata": {
9595
"contentType": "application/did+ld+json",
@@ -119,7 +119,6 @@ async def test_returns_deactivated_document(self, client: Client):
119119
"verificationMethod": [],
120120
},
121121
"didDocumentMetadata": {
122-
"versionId": None,
123122
"deactivated": True,
124123
},
125124
"didResolutionMetadata": {

0 commit comments

Comments
 (0)