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

fix: DIDCommV1: Routing keys & forward messages #45

Merged
merged 7 commits into from
Sep 17, 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
40 changes: 40 additions & 0 deletions didcomm_messaging/resolver/key.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"""DID Key Resolver."""

from didcomm_messaging.resolver import DIDResolver


class DIDKey(DIDResolver):
"""did:key resolver."""

async def is_resolvable(self, did: str) -> bool:
"""Check to see if DID is resolvable by this resolver."""
return did.startswith("did:key:")

async def resolve(self, did: str) -> dict:
"""Resolve a did:key."""
_, multikey = did.split("did:key:")
id = f"{did}#{multikey}"
return {
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/multikey/v1",
],
"id": did,
"verificationMethod": [
{
"id": id,
"type": "Multikey",
"controller": did,
"publicKeyMultibase": multikey,
}
],
**{
rel: [id]
for rel in (
"authentication",
"assertionMethod",
"capabilityDelegation",
"capabilityInvocation",
)
},
}
46 changes: 35 additions & 11 deletions didcomm_messaging/v1/messaging.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
from dataclasses import dataclass
import json
import uuid
from typing import Generic, Optional, Sequence, Union
from typing import Any, Generic, Optional, Sequence, Union

from pydantic import AnyUrl
from pydid import DIDDocument, VerificationMethod
from pydid import DIDDocument, DIDUrl, VerificationMethod
from pydid.service import DIDCommV1Service

from didcomm_messaging.crypto import P, S, SecretsManager
from didcomm_messaging.crypto.jwe import JweEnvelope
from didcomm_messaging.resolver import DIDResolver
from didcomm_messaging.v1.crypto.base import V1CryptoService
from didcomm_messaging.v1.packaging import V1PackagingService
Expand Down Expand Up @@ -58,14 +59,35 @@ class Target:
class V1DIDCommMessagingService(Generic[P, S]):
"""Main entrypoint for DIDComm Messaging."""

def vm_to_v1_kid(self, crypto: V1CryptoService, doc: DIDDocument, ref: str) -> str:
def local_vm_ref_to_v1_kid(
self, crypto: V1CryptoService, doc: DIDDocument, ref: str
) -> str:
"""Convert a verification method ref to a DIDComm v1 kid."""
return crypto.public_key_to_v1_kid(
crypto.verification_method_to_public_key(
doc.dereference_as(VerificationMethod, ref)
)
)

async def routing_key_to_kid(
self,
crypto: V1CryptoService[P, S],
resolver: DIDResolver,
doc: DIDDocument,
routing_key: DIDUrl,
) -> str:
"""Resolve routing key to a kid."""
if routing_key.did is None or routing_key.did == doc.id:
return self.local_vm_ref_to_v1_kid(crypto, doc, routing_key)

assert routing_key.did != doc.id
routing_key_vm = await resolver.resolve_and_dereference_verification_method(
routing_key
)
return crypto.public_key_to_v1_kid(
crypto.verification_method_to_public_key(routing_key_vm)
)

async def did_to_target(
self, crypto: V1CryptoService[P, S], resolver: DIDResolver, did: str
) -> Target:
Expand All @@ -81,10 +103,11 @@ async def did_to_target(
target = services[0]

recipient_keys = [
self.vm_to_v1_kid(crypto, doc, recip) for recip in target.recipient_keys
self.local_vm_ref_to_v1_kid(crypto, doc, recip)
for recip in target.recipient_keys
]
routing_keys = [
self.vm_to_v1_kid(crypto, doc, routing_key)
await self.routing_key_to_kid(crypto, resolver, doc, routing_key)
for routing_key in target.routing_keys
]
endpoint = target.service_endpoint
Expand Down Expand Up @@ -112,11 +135,12 @@ async def from_did_to_kid(
target = services[0]

recipient_keys = [
self.vm_to_v1_kid(crypto, doc, recip) for recip in target.recipient_keys
self.local_vm_ref_to_v1_kid(crypto, doc, recip)
for recip in target.recipient_keys
]
return recipient_keys[0]

def forward_wrap(self, to: str, msg: str) -> bytes:
def forward_wrap(self, to: str, msg: dict) -> bytes:
"""Wrap a message in a forward."""
forward = {
"@id": str(uuid.uuid4()),
Expand All @@ -132,7 +156,7 @@ async def pack(
resolver: DIDResolver,
secrets: SecretsManager[S],
packaging: V1PackagingService[P, S],
message: Union[dict, str, bytes],
message: Union[dict, str, bytes, Any],
to: Union[str, Target],
frm: Optional[str] = None,
**options,
Expand Down Expand Up @@ -187,7 +211,7 @@ async def pack(
encoded_message = await packaging.pack(
crypto,
secrets,
self.forward_wrap(forward_to, encoded_message.to_json()),
self.forward_wrap(forward_to, encoded_message.serialize()),
[routing_key],
)
forward_to = routing_key
Expand All @@ -199,7 +223,7 @@ async def unpack(
crypto: V1CryptoService[P, S],
secrets: SecretsManager[S],
packaging: V1PackagingService[P, S],
encoded_message: bytes,
encoded_message: Union[JweEnvelope, str, bytes, dict, Any],
**options,
) -> V1UnpackResult:
"""Unpack a message."""
Expand Down Expand Up @@ -262,7 +286,7 @@ async def pack(

async def unpack(
self,
encoded_message: bytes,
encoded_message: Union[JweEnvelope, str, bytes, dict, Any],
**options,
) -> V1UnpackResult:
"""Unpack a message."""
Expand Down
8 changes: 5 additions & 3 deletions didcomm_messaging/v1/packaging.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""V1PackagingService interface."""

from typing import Generic, Optional, Sequence, Tuple, Union
from typing import Any, Generic, Optional, Sequence, Tuple, Union

from didcomm_messaging.crypto.base import P, S, SecretsManager
from didcomm_messaging.crypto.jwe import JweEnvelope, JweRecipient
Expand Down Expand Up @@ -82,7 +82,7 @@ async def unpack(
self,
crypto: V1CryptoService[P, S],
secrets: SecretsManager[S],
enc_message: Union[JweEnvelope, str, bytes],
enc_message: Union[JweEnvelope, str, bytes, dict, Any],
) -> V1CryptoUnpackResult:
"""Unpack a DIDComm v1 message."""
if isinstance(enc_message, (str, bytes)):
Expand All @@ -92,8 +92,10 @@ async def unpack(
raise V1PackagingServiceError("Invalid packed message")
elif isinstance(enc_message, JweEnvelope):
wrapper = enc_message
elif isinstance(enc_message, dict):
wrapper = JweEnvelope.deserialize(enc_message)
else:
raise TypeError("Invalid enc_message")
raise TypeError("Invalid enc_message; expect envelope, str, bytes, or dict")

recip_key, recip_data = await self.extract_packed_message_metadata(
secrets, wrapper
Expand Down
Loading