Current section
Files
Jump to
Current section
Files
lib/nostr/crypto.ex
defmodule Nostr.Crypto do
@moduledoc """
Crypto related stuff
"""
@doc """
Get pubkey (hex encoded) from seckey (hex encoded)
## Example:
iex> Nostr.Crypto.pubkey("1111111111111111111111111111111111111111111111111111111111111111")
"4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa"
"""
@spec pubkey(seckey :: binary()) :: binary()
def pubkey(seckey) do
seckey
|> d16()
|> Secp256k1.pubkey(:xonly)
|> e16()
end
@doc """
Sign binary data (hex encoded) with seckey (hex encoded)
"""
@spec sign(data :: binary(), seckey :: binary()) :: binary()
def sign(data, seckey) do
data
|> d16()
|> Secp256k1.schnorr_sign(d16(seckey))
|> e16()
end
@doc """
Encrypt message with ECDH from seckey and pubkey and append randomly generated IV
"""
@spec encrypt(String.t(), binary(), binary()) :: binary()
def encrypt(message, seckey, pubkey) do
iv = :crypto.strong_rand_bytes(16)
cipher_text =
:crypto.crypto_one_time(:aes_256_cbc, shared_secret(seckey, pubkey), iv, message,
encrypt: true,
padding: :pkcs_padding
)
e64(cipher_text) <> "?iv=" <> e64(iv)
end
@doc """
Decrypt message with ECDH from seckey and pubkey (automatically extracts IV from message)
"""
@spec decrypt(binary(), binary(), binary()) :: String.t()
def decrypt(message, seckey, pubkey) do
[message, iv] = String.split(message, "?iv=")
:crypto.crypto_one_time(:aes_256_cbc, shared_secret(seckey, pubkey), d64(iv), d64(message),
encrypt: false,
padding: :pkcs_padding
)
end
defp shared_secret(seckey, pubkey) when is_binary(seckey) and is_binary(pubkey) do
:crypto.compute_key(:ecdh, d16("02" <> pubkey), d16(seckey), :secp256k1)
end
defp d16(data), do: Base.decode16!(data, case: :lower)
defp d64(data), do: Base.decode64!(data)
defp e16(data), do: Base.encode16(data, case: :lower)
defp e64(data), do: Base.encode64(data)
end