Skip to main content

lib/amarula/protocol/signal/group/sender_key_distribution_message.ex

defmodule Amarula.Protocol.Signal.Group.SenderKeyDistributionMessage do
  @moduledoc """
  Sender key distribution message, ported from
  `src/Signal/Group/sender-key-distribution-message.ts`.

  Wire format: `[version byte][Proto.SenderKeyDistributionMessage]` where
  version = `(3 << 4) | 3 = 0x33`. No signature — the SKDM travels inside an
  already-encrypted Signal message.
  """

  import Bitwise

  alias Amarula.Protocol.Proto

  @current_version 3

  @type t :: %__MODULE__{
          id: integer(),
          iteration: integer(),
          chain_key: binary(),
          signature_key: binary(),
          serialized: binary()
        }

  defstruct id: nil, iteration: nil, chain_key: nil, signature_key: nil, serialized: nil

  @doc """
  Creates a new SenderKeyDistributionMessage. `signature_key` is the public
  signing key in wire form (33 bytes, 0x05-prefixed).
  """
  @spec new(integer(), integer(), binary(), binary()) :: t()
  def new(id, iteration, chain_key, signature_key) do
    version = @current_version <<< 4 ||| @current_version

    message =
      Proto.SenderKeyDistributionMessage.encode(%Proto.SenderKeyDistributionMessage{
        id: id,
        iteration: iteration,
        chainKey: chain_key,
        signingKey: signature_key
      })

    %__MODULE__{
      id: id,
      iteration: iteration,
      chain_key: chain_key,
      signature_key: signature_key,
      serialized: <<version>> <> message
    }
  end

  @doc """
  Parses a serialized SenderKeyDistributionMessage (`[version][protobuf]`).
  Malformed protobuf raises — let it crash.
  """
  @spec from_serialized(binary()) :: {:ok, t()} | {:error, String.t()}
  def from_serialized(<<_version, message::binary>> = serialized) do
    msg = Proto.SenderKeyDistributionMessage.decode(message)

    {:ok,
     %__MODULE__{
       id: msg.id,
       iteration: msg.iteration,
       chain_key: msg.chainKey,
       signature_key: msg.signingKey,
       serialized: serialized
     }}
  end

  def from_serialized(_), do: {:error, "Serialized SKDM too short"}

  @doc """
  Gets the ID of the sender key.
  """
  @spec get_id(t()) :: integer()
  def get_id(%__MODULE__{id: id}), do: id

  @doc """
  Gets the iteration count.
  """
  @spec get_iteration(t()) :: integer()
  def get_iteration(%__MODULE__{iteration: iteration}), do: iteration

  @doc """
  Gets the chain key.
  """
  @spec get_chain_key(t()) :: binary()
  def get_chain_key(%__MODULE__{chain_key: chain_key}), do: chain_key

  @doc """
  Gets the signature key.
  """
  @spec get_signature_key(t()) :: binary()
  def get_signature_key(%__MODULE__{signature_key: signature_key}), do: signature_key

  @doc """
  Gets the serialized message.
  """
  @spec get_serialized(t()) :: binary()
  def get_serialized(%__MODULE__{serialized: serialized}), do: serialized
end