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

DIDComm v1 interfaces like those available for v2 #39

Merged
merged 8 commits into from
Jun 6, 2024
Merged
Show file tree
Hide file tree
Changes from 5 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
176 changes: 13 additions & 163 deletions didcomm_messaging/__init__.py
Original file line number Diff line number Diff line change
@@ -1,170 +1,20 @@
"""DIDComm Messaging."""

from dataclasses import dataclass
import json
from typing import Generic, Optional, List

from pydid.service import DIDCommV2Service

from didcomm_messaging.crypto import CryptoService, SecretsManager, P, S
from didcomm_messaging.crypto import CryptoService, P, S, SecretsManager
from didcomm_messaging.messaging import DIDCommMessaging, DIDCommMessagingService
from didcomm_messaging.packaging import PackagingService
from didcomm_messaging.resolver import DIDResolver
from didcomm_messaging.routing import RoutingService


@dataclass
class PackResult:
"""Result of packing a message."""

message: bytes
target_services: List[DIDCommV2Service]

def get_endpoint(self, protocol: str) -> str:
"""Get the first matching endpoint to send the message to."""
return self.get_service(protocol).service_endpoint.uri

def get_service(self, protocol: str) -> DIDCommV2Service:
"""Get the first matching service to send the message to."""
return self.filter_services_by_protocol(protocol)[0]

def filter_services_by_protocol(self, protocol: str) -> List[DIDCommV2Service]:
"""Get all services that start with a specific uri protocol."""
return [
service
for service in self.target_services
if service.service_endpoint.uri.startswith(protocol)
]


@dataclass
class UnpackResult:
"""Result of unpacking a message."""

message: dict
encrytped: bool
authenticated: bool
recipient_kid: str
sender_kid: Optional[str] = None


class DIDCommMessagingService(Generic[P, S]):
"""Main entrypoint for DIDComm Messaging."""

def service_to_target(self, service: DIDCommV2Service) -> str:
"""Convert a service to a target uri.

This is a very simple implementation that just returns the first one.
"""
if isinstance(service.service_endpoint, list):
service_endpoint = service.service_endpoint[0]
else:
service_endpoint = service.service_endpoint

return service_endpoint.uri

async def pack(
self,
crypto: CryptoService[P, S],
resolver: DIDResolver,
secrets: SecretsManager[S],
packaging: PackagingService[P, S],
routing: RoutingService,
message: dict,
to: str,
frm: Optional[str] = None,
**options,
):
"""Pack a message."""
# TODO crypto layer permits packing to multiple recipients; should we as well?

encoded_message = await packaging.pack(
crypto,
resolver,
secrets,
json.dumps(message).encode(),
[to],
frm,
**options,
)

forward, services = await routing.prepare_forward(
crypto, packaging, resolver, secrets, to, encoded_message
)
return PackResult(forward, services)

async def unpack(
self,
crypto: CryptoService[P, S],
resolver: DIDResolver,
secrets: SecretsManager[S],
packaging: PackagingService[P, S],
encoded_message: bytes,
**options,
) -> UnpackResult:
"""Unpack a message."""
unpacked, metadata = await packaging.unpack(
crypto, resolver, secrets, encoded_message, **options
)
message = json.loads(unpacked.decode())
return UnpackResult(
message,
encrytped=bool(metadata.method),
authenticated=bool(metadata.sender_kid),
recipient_kid=metadata.recip_key.kid,
sender_kid=metadata.sender_kid,
)


class DIDCommMessaging(Generic[P, S]):
"""Main entrypoint for DIDComm Messaging."""

def __init__(
self,
crypto: CryptoService[P, S],
secrets: SecretsManager[S],
resolver: DIDResolver,
packaging: PackagingService[P, S],
routing: RoutingService,
):
"""Initialize the DIDComm Messaging service."""
self.crypto = crypto
self.secrets = secrets
self.resolver = resolver
self.packaging = packaging
self.routing = routing
self.dmp = DIDCommMessagingService()

async def pack(
self,
message: dict,
to: str,
frm: Optional[str] = None,
**options,
) -> PackResult:
"""Pack a message."""
return await self.dmp.pack(
self.crypto,
self.resolver,
self.secrets,
self.packaging,
self.routing,
message,
to,
frm,
**options,
)

async def unpack(
self,
encoded_message: bytes,
**options,
) -> UnpackResult:
"""Unpack a message."""
return await self.dmp.unpack(
self.crypto,
self.resolver,
self.secrets,
self.packaging,
encoded_message,
**options,
)
__all__ = [
"CryptoService",
"DIDCommMessaging",
"DIDCommMessagingService",
"DIDResolver",
"P",
"PackagingService",
"RoutingService",
"S",
"SecretsManager",
]
5 changes: 5 additions & 0 deletions didcomm_messaging/crypto/backend/askar.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,11 @@ def multikey(self) -> str:
"""Get the key in multibase format."""
return self._multikey

@property
def key_bytes(self) -> bytes:
"""Get the bytes of the key."""
return self.key.get_public_bytes()


class AskarSecretKey(SecretKey):
"""Secret key implementation for Askar."""
Expand Down
12 changes: 12 additions & 0 deletions didcomm_messaging/crypto/backend/authlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,18 @@ def multikey(self) -> str:
"""Return the key in multikey format."""
return self._multikey

@property
def key_bytes(self) -> bytes:
"""Get the bytes of the key."""
jwk = self.key.as_dict(is_private=False)
codec = self.kty_crv_to_codec.get((jwk["kty"], jwk.get("crv")))

if not codec:
raise ValueError("Unsupported key type")

key_bytes = b64url.decode(jwk["x"])
return key_bytes

@classmethod
def key_to_multikey(cls, key: AsymmetricKey) -> str:
"""Convert an Authlib key to a multikey."""
Expand Down
16 changes: 11 additions & 5 deletions didcomm_messaging/crypto/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,12 @@ def key_bytes_from_verification_method(cls, vm: VerificationMethod) -> bytes:
return decoded
else:
codec, decoded = multicodec.unwrap(decoded)
expected_codec = cls.type_to_codec.get(vm.type)
if not expected_codec:
raise ValueError("Unsupported verification method type")
if codec.name != expected_codec:
raise ValueError("Type and codec mismatch")
if vm.type != "Multikey":
expected_codec = cls.type_to_codec.get(vm.type)
if not expected_codec:
raise ValueError("Unsupported verification method type")
if codec.name != expected_codec:
raise ValueError("Type and codec mismatch")
return decoded

if vm.public_key_base58:
Expand All @@ -68,6 +69,11 @@ def kid(self) -> str:
def multikey(self) -> str:
"""Get the key in multikey format."""

@property
@abstractmethod
def key_bytes(self) -> bytes:
"""Get the bytes of the key."""


class SecretKey(ABC):
"""Secret Key Type."""
Expand Down
2 changes: 1 addition & 1 deletion didcomm_messaging/crypto/jwe.py
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ def _deserialize(cls, parsed: Mapping[str, Any]) -> "JweEnvelope": # noqa: C901
inst = cls(
recipients=recipients,
protected=protected,
protected_b64=protected_b64,
protected_b64=protected_b64.encode(),
unprotected=unprotected,
ciphertext=ciphertext,
iv=iv,
Expand Down
10 changes: 10 additions & 0 deletions didcomm_messaging/legacy/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
"""Legacy DIDComm v1 Interfaces.

These components are intended to provide a similar structure to the DIDComm v2
interfaces provided by this library. While the community is transitioning from
DIDComm v1 to v2, having a consistent interface to interact with will help
implementers to support both versions until the transition is complete.

It is expected that a future version of this library will eventually remove
these interfaces.
"""
Loading