Packages
baobab
0.16.8
0.35.1
0.35.0
0.34.0
0.31.0
0.30.0
0.21.2
0.21.1
0.21.0
0.20.0
0.19.2
0.19.1
0.19.0
0.18.1
0.18.0
0.17.1
0.17.0
0.16.8
0.16.7
0.16.6
0.16.5
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.2
0.15.1
0.15.0
0.14.1
0.14.0
0.13.1
0.13.0
0.12.1
0.12.0
0.11.1
0.11.0
0.10.6
0.10.5
0.10.4
0.10.3
0.10.2
0.10.1
0.10.0
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.3
0.8.2
0.8.1
0.8.0
0.7.0
0.6.0
0.5.2
0.5.1
0.5.0
0.4.0
0.3.3
0.3.2
0.3.1
0.3.0
0.2.1
0.2.0
0.1.0
Bamboo append-only logs
Current section
Files
Jump to
Current section
Files
lib/baobab/identity.ex
defmodule Baobab.Identity do
alias Baobab.Persistence
@moduledoc """
Functions related too Baobab identity (keypair) handling
"""
BaseX.prepare_module(
"Base62",
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
32
)
@doc """
Resolve an identity to its Base62 representation
Attempts to resolve `~short` using stored logs
"""
@spec as_base62(String.t()) :: String.t() | {:error, String.t()}
def as_base62(identity)
def as_base62(id) when not is_binary(id), do: {:error, "Unresolvable identity"}
# Looks like a short base62
def as_base62(<<"~", short::binary>>) do
case Enum.filter(stored_authors(), fn a -> String.starts_with?(a, short) end) do
[] -> {:error, "Unknown identity: ~" <> short}
[id] -> id
_ -> {:error, "Ambiguous identity: ~" <> short}
end
end
# Looks like a base62-encoded key
def as_base62(identity) when byte_size(identity) == 43, do: identity
# Looks like a proper key
def as_base62(identity) when byte_size(identity) == 32, do: BaseX.Base62.encode(identity)
# I guess it's a stored identity?
def as_base62(identity) do
case key(identity, :public) do
:error -> {:error, "Unknown identity"}
key -> BaseX.Base62.encode(key)
end
end
defp stored_authors() do
Baobab.stored_info() |> Enum.map(fn {a, _, _} -> a end) |> Enum.uniq()
end
@doc """
Create and store a new identity string
An optional secret key to be associated with the identity may provided, either
raw or base62 encoded. The public key will be derived therefrom.
"""
@spec create(String.t(), binary | nil) ::
String.t() | {:error, String.t()}
def create(identity, secret_key \\ nil)
def create(identity, nil), do: create(identity, :crypto.strong_rand_bytes(32))
def create(identity, sk) when byte_size(sk) == 43 do
try do
create(identity, BaseX.Base62.decode(sk))
rescue
_ -> {:error, "Improper Base62 key"}
end
end
def create(identity, secret_key)
when is_binary(identity) and is_binary(secret_key) and byte_size(secret_key) == 32 do
# Despite appearances, enacl does not derive public
# from secret. Instead it counts on the fact that the
# two are concatenated. So this stays.
pair = {secret_key, Ed25519.derive_public_key(secret_key)}
ident_store(:put, {identity, pair})
elem(pair, 1) |> as_base62
end
def create(_, _), do: {:error, "Improper arguments"}
@doc """
Rename an extant identity leaving its keys intact.
"""
@spec rename(String.t(), String.t()) :: String.t() | {:error, String.t()}
# No guard against extant non-string to allow migration
def rename(identity, new_name) when is_binary(new_name) do
{sk, _} = ident_store(:get, identity)
ident_store(:delete, identity)
# We'll do the extra work to regen the public key
create(new_name, sk)
end
def rename(_, _), do: {:error, "Identities must be strings"}
@doc """
Drop a stored identity. `Baobab` will be unable to recover keys
(notably `:secret` keys) destroyed herewith.
"""
@spec drop(String.t()) :: :ok | {:error, String.t()}
# I am not removing the ability to drop identities which can no
# longer be created. If it's in there the consumer should be able to get it out
def drop(identity) do
case ident_store(:get, identity) do
{_sk, _pk} -> ident_store(:delete, identity)
_ -> {:error, "No such identity"}
end
end
@doc """
Retrieve the key for a stored identity.
Can be either the `:public` or `:secret` key
"""
@spec key(String.t(), atom) :: binary | :error
def key(identity, which) do
case ident_store(:get, identity) do
{secret, public} ->
case which do
:secret -> secret
:public -> public
:signing -> secret <> public
_ -> :error
end
_ ->
:error
end
end
@doc """
List all known identities with their base62 public key representation
"""
@spec list() :: [{String.t(), String.t()}]
def list() do
ident_store(:foldl, fn item, acc ->
case item do
{a, {_, public}} -> [{a, as_base62(public)} | acc]
_ -> acc
end
end)
|> Enum.sort()
end
@doc false
def ident_store(action, value \\ nil), do: Persistence.action(:identity, "", action, value)
end