defmodule RNS.Crypto do @moduledoc """ Crypto primitives for Reticulum. Lazily copied from https://github.com/Sgiath/reticulum. Usually, you won't use this module but the Identity module, which then uses this module. """ @type hash() :: <<_::256>> @type truncated_hash() :: <<_::128>> @type key() :: <<_::256>> @type signature() :: <<_::512>> @type ratchet() :: <<_::256>> @hash_len 32 @doc """ https://soatok.blog/2021/11/17/understanding-hkdf/ https://en.wikipedia.org/wiki/HKDF """ @spec hkdf(binary(), binary(), binary(), non_neg_integer()) :: <<_::512>> def hkdf(ikm, salt \\ <<>>, info \\ <<>>, len \\ 64) when len > 0 and is_binary(ikm) and byte_size(ikm) > 0 do salt |> hkdf_extract(ikm) |> hkdf_expand(info, len) end @spec hkdf_extract(binary(), binary()) :: binary() defp hkdf_extract(<<>>, ikm), do: hkdf_extract(<<0::size(256)>>, ikm) defp hkdf_extract(salt, ikm), do: hmac(salt, ikm) @spec hkdf_expand(binary(), binary(), non_neg_integer()) :: binary() defp hkdf_expand(prk, info, len) do {<>, _t} = Enum.reduce(1..ceil(len / @hash_len), {<<>>, <<>>}, fn i, {okm, t} -> t = hmac(prk, t <> info <> <>) {okm <> t, t} end) okm end @spec truncate(binary(), non_neg_integer()) :: binary() def truncate(data, length) do <> = data truncated_data end @spec sha256(binary()) :: hash() def sha256(data), do: :crypto.hash(:sha256, data) @spec truncated_sha256(binary()) :: truncated_hash() def truncated_sha256(data) do truncate(:crypto.hash(:sha256, data), 16) end @spec hmac(iodata(), binary()) :: <<_::256>> def hmac(key, data) do :crypto.mac(:hmac, :sha256, key, data) end @spec ed25519() :: {key(), key()} def ed25519() do :crypto.generate_key(:eddsa, :ed25519) end @spec x25519() :: {key(), key()} def x25519() do :crypto.generate_key(:eddh, :x25519) end @spec compute(key(), key()) :: key() def compute(secret, public) do :crypto.compute_key(:eddh, public, secret, :x25519) end @spec sign(key(), binary()) :: signature() def sign(key, data) do :crypto.sign(:eddsa, :none, data, [key, :ed25519]) end @spec validate?(key(), binary(), signature()) :: boolean() def validate?(key, data, signature) do :crypto.verify(:eddsa, :none, data, signature, [key, :ed25519]) end end