defmodule Nostr.Crypto.AES256CBC do
@moduledoc """
Algorithm that encrypts and decrypts direct messages
"""
@spec encrypt(
String.t(),
K256.Schnorr.signing_key() | <<_::256>>,
K256.Schnorr.verifying_key() | <<_::256>>
) :: String.t()
def encrypt(message, seckey, pubkey) do
iv = :crypto.strong_rand_bytes(16)
shared_secret = get_shared_secret(seckey, pubkey)
cipher_text =
:crypto.crypto_one_time(:aes_256_cbc, shared_secret, iv, message,
encrypt: true,
padding: :pkcs_padding
)
b64_cypher_text = Base.encode64(cipher_text)
b64_iv = Base.encode64(iv)
"#{b64_cypher_text}?iv=#{b64_iv}"
end
@spec decrypt(String.t(), K256.Schnorr.signing_key(), K256.Schnorr.verifying_key()) ::
{:ok, String.t()} | {:error, atom() | String.t()}
def decrypt(message, seckey, pubkey) do
[message, iv] = String.split(message, "?iv=")
with {:ok, message} <- Base.decode64(message),
{:ok, iv} <- Base.decode64(iv) do
shared_secret = get_shared_secret(seckey, pubkey)
decrypted =
:crypto.crypto_one_time(:aes_256_cbc, shared_secret, iv, message,
encrypt: false,
padding: :pkcs_padding
)
{:ok, decrypted}
else
{:error, message} -> {:error, message}
:error -> {:error, "cannot decode iv, which should be in base64"}
end
end
defp get_shared_secret(<<_::256>> = seckey, <<_::256>> = pubkey) do
:crypto.compute_key(
:ecdh,
<<0x02::8, pubkey::bitstring-256>>,
seckey,
:secp256k1
)
end
end