Source code for syft.core.common.message

# stdlib
import sys
from typing import Generic
from typing import Optional
from typing import Type
from typing import TypeVar

# third party
from google.protobuf.reflection import GeneratedProtocolMessageType
from nacl.exceptions import BadSignatureError
from nacl.signing import SigningKey
from nacl.signing import VerifyKey

# syft relative
from ...core.common.object import ObjectWithID
from ...core.common.serde.serialize import _serialize as serialize
from ...core.common.uid import UID
from ...core.io.address import Address
from ...logger import debug
from ...logger import traceback_and_raise
from ...proto.core.auth.signed_message_pb2 import SignedMessage as SignedMessage_PB
from ...util import get_fully_qualified_name
from ...util import validate_type
from ..common.serde.deserialize import _deserialize
from ..common.serde.serializable import bind_protobuf

# this generic type for SignedMessage
SignedMessageT = TypeVar("SignedMessageT")


class AbstractMessage(ObjectWithID, Generic[SignedMessageT]):
    """ """

    # this should be overloaded by a subclass
    signed_type: Type[SignedMessageT]

    @property
    def class_name(self) -> str:
        return str(self.__class__.__name__)

    @property
    def icon(self) -> str:
        icon = "✉️ "
        if "signed" in self.class_name.lower():
            icon += "🔏"
        return icon

    @property
    def pprint(self) -> str:
        return f"{self.icon} ({self.class_name})"

    def post_init(self) -> None:
        init_reason = "Creating"
        if "signed" in self.class_name.lower():
            init_reason += " Signed"
        debug(f"> {init_reason} {self.pprint} {self.id.emoji()}")


[docs]class SyftMessage(AbstractMessage): """ SyftMessages are an abstraction that represents information that is sent between a :class:`Client` and a :class:`Node`. In Syft's decentralized setup, we can easily see why SyftMessages are so important. This class cannot be used as-is: to get some useful objects, we need to derive from it. For instance, :class:`Action`s inherit from :class:`SyftMessage`. There are many types of SyftMessage which boil down to whether or not they are Sync or Async, and whether or not they expect a response. Attributes: address: the :class:`Address` to which the message needs to be delivered. """ def __init__(self, address: Address, msg_id: Optional[UID] = None) -> None: self.address = address super().__init__(id=msg_id) self.post_init()
[docs] def sign(self, signing_key: SigningKey) -> SignedMessageT: """ It's important for all messages to be able to prove who they were sent from. This method endows every message with the ability for someone to "sign" (with a hash of the message) as the sender of the message so that someone else, at a later date, can verify the sender. Args: signing_key: The key to use to sign the SyftMessage. Returns: A :class:`SignedMessage` """ debug(f"> Signing with {self.address.key_emoji(key=signing_key.verify_key)}") signed_message = signing_key.sign(serialize(self, to_bytes=True)) # signed_type will be the final subclass callee's closest parent signed_type # for example ReprMessage -> ImmediateSyftMessageWithoutReply.signed_type # == SignedImmediateSyftMessageWithoutReply return self.signed_type( msg_id=self.id, address=self.address, obj_type=get_fully_qualified_name(obj=self), signature=signed_message.signature, verify_key=signing_key.verify_key, message=signed_message.message, )
[docs]@bind_protobuf class SignedMessage(SyftMessage): """ SignedMessages are :class:`SyftMessage`s that have been signed by someone. In addition to what has a :class:`SyftMessage`, they have a signature, a verify key and a :meth:`is_valid` property that is here to check that the message was really signed and sent by the verify key owner. Attributes: obj_type (string): the string representation of the type of the original message. signature (bytes): the signature of the message. verify_key (VerifyKey): the signer's public key with which the signature can be verified. serialized_message: the serialized original message. """ obj_type: str signature: bytes verify_key: VerifyKey def __init__( self, address: Address, obj_type: str, signature: bytes, verify_key: VerifyKey, message: bytes, msg_id: Optional[UID] = None, ) -> None: super().__init__(msg_id=msg_id, address=address) self.obj_type = obj_type self.signature = signature self.verify_key = verify_key self.serialized_message = message self.cached_deseralized_message: Optional[SyftMessage] = None @property def message(self) -> "SyftMessage": if self.cached_deseralized_message is None: _syft_msg = validate_type( _deserialize(blob=self.serialized_message, from_bytes=True), SyftMessage ) self.cached_deseralized_message = _syft_msg if self.cached_deseralized_message is None: traceback_and_raise( ValueError( f"Can't deserialize message {self} with address " f"{self.address}" ) ) return self.cached_deseralized_message @property def is_valid(self) -> bool: try: _ = self.verify_key.verify(self.serialized_message, self.signature) except BadSignatureError: return False return True def _object2proto(self) -> SignedMessage_PB: debug(f"> {self.icon} -> Proto 🔢 {self.id}") # obj_type will be the final subclass callee for example ReprMessage return SignedMessage_PB( msg_id=serialize(self.id, to_proto=True), obj_type=self.obj_type, signature=bytes(self.signature), verify_key=bytes(self.verify_key), message=self.serialized_message, ) @staticmethod def _proto2object(proto: SignedMessage_PB) -> SignedMessageT: # TODO: horrible temp hack, need to rethink address on SignedMessage sub_message = validate_type( _deserialize(blob=proto.message, from_bytes=True), SyftMessage ) address = sub_message.address # proto.obj_type is final subclass callee for example ReprMessage # but we want the associated signed_type which is # ReprMessage -> ImmediateSyftMessageWithoutReply.signed_type # == SignedImmediateSyftMessageWithoutReply module_parts = proto.obj_type.split(".") klass = module_parts.pop() obj_type = getattr(sys.modules[".".join(module_parts)], klass) obj = obj_type.signed_type( msg_id=_deserialize(blob=proto.msg_id), address=address, obj_type=proto.obj_type, signature=proto.signature, verify_key=VerifyKey(proto.verify_key), message=proto.message, ) icon = "🤷🏾‍♀️" if hasattr(obj, "icon"): icon = obj.icon debug(f"> {icon} <- 🔢 Proto") if type(obj) != obj_type.signed_type: traceback_and_raise( TypeError( "Deserializing SignedMessage. " + f"Expected type {obj_type.signed_type}. Got {type(obj)}" ) ) return obj
[docs] @staticmethod def get_protobuf_schema() -> GeneratedProtocolMessageType: return SignedMessage_PB
class SignedImmediateSyftMessageWithReply(SignedMessage): """ """ class SignedImmediateSyftMessageWithoutReply(SignedMessage): """ """ # do stuff class SignedEventualSyftMessageWithoutReply(SignedMessage): """ """ class ImmediateSyftMessage(SyftMessage): """ """ class EventualSyftMessage(SyftMessage): """ """ class SyftMessageWithReply(SyftMessage): """ """ class SyftMessageWithoutReply(SyftMessage): """ """ class ImmediateSyftMessageWithoutReply(ImmediateSyftMessage, SyftMessageWithoutReply): signed_type = SignedImmediateSyftMessageWithoutReply def __init__(self, address: Address, msg_id: Optional[UID] = None) -> None: super().__init__(address=address, msg_id=msg_id) class EventualSyftMessageWithoutReply(EventualSyftMessage, SyftMessageWithoutReply): signed_type = SignedEventualSyftMessageWithoutReply def __init__(self, address: Address, msg_id: Optional[UID] = None) -> None: super().__init__(address=address, msg_id=msg_id) class ImmediateSyftMessageWithReply(ImmediateSyftMessage, SyftMessageWithReply): signed_type = SignedImmediateSyftMessageWithReply def __init__( self, reply_to: Address, address: Address, msg_id: Optional[UID] = None ) -> None: super().__init__(address=address, msg_id=msg_id) self.reply_to = reply_to