diff options
author | Alex Auvolat <alex@adnab.me> | 2018-09-26 14:37:10 +0200 |
---|---|---|
committer | Alex Auvolat <alex@adnab.me> | 2018-09-26 14:44:29 +0200 |
commit | f16973d3a492ae6d4890c40d77b0a93d3293bf3a (patch) | |
tree | 36a38ec318ed9d0f87d28d508169271729b8507a | |
parent | 1df3aa74a6870f276bead1ed5650f0d86355ce09 (diff) | |
download | shard-f16973d3a492ae6d4890c40d77b0a93d3293bf3a.tar.gz shard-f16973d3a492ae6d4890c40d77b0a93d3293bf3a.zip |
Signing and stuff
-rw-r--r-- | shard/config/config.exs | 6 | ||||
-rw-r--r-- | shard/lib/app/chat.ex | 77 | ||||
-rw-r--r-- | shard/lib/app/identity.ex | 113 | ||||
-rw-r--r-- | shard/lib/application.ex | 2 | ||||
-rw-r--r-- | shard/lib/cli/cli.ex | 109 | ||||
-rw-r--r-- | shard/lib/identity.ex | 65 | ||||
-rw-r--r-- | shard/lib/keys.ex | 124 | ||||
-rw-r--r-- | shard/lib/manager.ex | 24 | ||||
-rw-r--r-- | shard/lib/net/tcpconn.ex | 2 | ||||
-rw-r--r-- | shard/mix.exs | 1 | ||||
-rw-r--r-- | shard/mix.lock | 5 | ||||
-rw-r--r-- | shardweb/config/config.exs | 1 |
12 files changed, 400 insertions, 129 deletions
diff --git a/shard/config/config.exs b/shard/config/config.exs index f2a7f09..a5aa071 100644 --- a/shard/config/config.exs +++ b/shard/config/config.exs @@ -32,6 +32,12 @@ use Mix.Config # it is not recommended to use long suffixes. config :shard, peer_id_suffix: "SH" +# Identity suffix +# =============== +# This Shard instance will only accept messages by identities whose +# key ends by this suffix +config :shard, identity_suffix: "ID" + # Data directory # ============== config :shard, data_path: Path.join [System.user_home, "shard", "data"] diff --git a/shard/lib/app/chat.ex b/shard/lib/app/chat.ex index db2cb64..e61d648 100644 --- a/shard/lib/app/chat.ex +++ b/shard/lib/app/chat.ex @@ -28,7 +28,7 @@ defmodule SApp.Chat do defimpl Shard.Manifest, for: Manifest do def start(m) do - SApp.Chat.start_link(m.channel) + DynamicSupervisor.start_child(Shard.DynamicSupervisor, {SApp.Chat, m.channel}) end end @@ -69,6 +69,12 @@ defmodule SApp.Chat do end end + def find_proc(chan) do + manifest = %Manifest{channel: chan} + id = SData.term_hash manifest + Shard.Manager.find_proc id + end + @doc """ Implementation of the :manifest call that returns the chat room's manifest """ @@ -98,10 +104,11 @@ defmodule SApp.Chat do to send a message to the chat room. Puts the message in the store and syncs with all connected peers. """ - def handle_cast({:chat_send, msg}, state) do - msgitem = {(System.os_time :seconds), - Shard.Identity.get_nickname(), - msg} + def handle_cast({:chat_send, pk, msg}, state) do + msgbin = SData.term_bin {(System.os_time :seconds), msg} + {:ok, sign} = Shard.Keys.sign_detached(pk, msgbin) + msgitem = {pk, msgbin, sign} + prev_root = state.mst.root mst = MST.insert(state.mst, msgitem) state = %{state | mst: mst} @@ -157,21 +164,26 @@ defmodule SApp.Chat do state else # Try adding the message - if prev_root == state.mst.root do - mst2 = MST.insert(state.mst, msgitem) - if mst2.root == new_root do - # This was the only message missing, we are happy! - state = %{state | mst: mst2} - Shard.Manager.save_state(state.id, mst2.root) - GenServer.cast(state.page_store, {:set_roots, [mst2.root]}) - msg_callback(state, msgitem) - state + {pk, bin, sign} = msgitem + if Shard.Keys.verify(pk, bin, sign) == :ok do + if prev_root == state.mst.root do + mst2 = MST.insert(state.mst, msgitem) + if mst2.root == new_root do + # This was the only message missing, we are happy! + state = %{state | mst: mst2} + Shard.Manager.save_state(state.id, mst2.root) + GenServer.cast(state.page_store, {:set_roots, [mst2.root]}) + msg_callback(state, msgitem) + state + else + Logger.warn("Invalid new root after inserting same message item!") + state + end else - # More messages are missing, start a full merge init_merge(state, new_root, peer_id) end else - init_merge(state, new_root, peer_id) + state end end {:root, new_root} -> @@ -190,21 +202,33 @@ defmodule SApp.Chat do defp init_merge(state, new_root, source_peer) do # TODO: make the merge asynchronous + + Logger.info("Starting merge for #{inspect state.manifest}, merging root: #{new_root|>Base.encode16}") + prev_last = for {x, true} <- MST.last(state.mst, nil, 100), into: MapSet.new, do: x mgmst = %{state.mst | root: new_root} mgmst = put_in(mgmst.store.prefer_ask, [source_peer]) mst = MST.merge(state.mst, mgmst) - for {x, true} <- MST.last(mst, nil, 100) do + correct = for {x, true} <- MST.last(mst, nil, 100) do if not MapSet.member? prev_last, x do msg_callback(state, x) + {pk, bin, sign} = x + Shard.Keys.verify(pk, bin, sign) + else + true end end - GenServer.cast(state.page_store, {:set_roots, [mst.root]}) - Shard.Manager.save_state(state.id, mst.root) - %{state | mst: mst} + if Enum.all? correct do + GenServer.cast(state.page_store, {:set_roots, [mst.root]}) + Shard.Manager.save_state(state.id, mst.root) + %{state | mst: mst} + else + Logger.warn("Incorrect signatures somewhere while merging, dropping merged data") + state + end end def handle_info({:DOWN, _ref, :process, pid, _reason}, state) do @@ -212,20 +236,23 @@ defmodule SApp.Chat do {:noreply, %{ state | subs: new_subs }} end - defp msg_callback(state, {ts, nick, msg}) do + defp msg_callback(state, {pk, msgbin, _sign}) do + {ts, msg} = SData.term_unbin msgbin for pid <- state.subs do if Process.alive?(pid) do - send(pid, {:chat_recv, state.channel, {ts, nick, msg}}) + send(pid, {:chat_recv, state.channel, {ts, pk, msg}}) end end end - defp msg_cmp({ts1, nick1, msg1}, {ts2, nick2, msg2}) do + defp msg_cmp({pk1, msgbin1, _sign1}, {pk2, msgbin2, _sign2}) do + {ts1, msg1} = SData.term_unbin msgbin1 + {ts2, msg2} = SData.term_unbin msgbin2 cond do ts1 > ts2 -> :after ts1 < ts2 -> :before - nick1 > nick2 -> :after - nick1 < nick2 -> :before + pk1 > pk2 -> :after + pk1 < pk2 -> :before msg1 > msg2 -> :after msg1 < msg2 -> :before true -> :duplicate diff --git a/shard/lib/app/identity.ex b/shard/lib/app/identity.ex new file mode 100644 index 0000000..7e97897 --- /dev/null +++ b/shard/lib/app/identity.ex @@ -0,0 +1,113 @@ +defmodule SApp.Identity do + use GenServer + + require Logger + + defmodule Manifest do + defstruct [:pk] + end + + defmodule State do + defstruct [:info, :rev, :signed] + end + + defimpl Shard.Manifest, for: Manifest do + def start(m) do + DynamicSupervisor.start_child(Shard.DynamicSupervisor, {SApp.Identity, m.pk}) + end + end + + def start_link(pk) do + GenServer.start_link(__MODULE__, pk) + end + + def init(pk) do + manifest = %Manifest{pk: pk} + id = SData.term_hash manifest + + case Shard.Manager.register(id, manifest, self()) do + :ok -> + Shard.Manager.dispatch_to(id, nil, self()) + state = case Shard.Manager.load_state(id) do + nil -> + info = %{nick: default_nick(pk)} + SData.SignRev.new info + st -> + st + end + GenServer.cast(self(), :init_pull) + {:ok, %{pk: pk, id: id, state: state}} + :redundant -> + exit(:redundant) + end + end + + def default_nick(pk) do + nick_suffix = pk + |> binary_part(0, 4) + |> Base.encode16 + |> String.downcase + "Anon" <> nick_suffix + end + + def find_proc(pk) do + manifest = %Manifest{pk: pk} + id = SData.term_hash manifest + Shard.Manager.find_proc id + end + + def handle_call(:manifest, _from, state) do + {:replyl, state.manifest, state} + end + + def handle_call(:get_info, _from, state) do + {:reply, SData.SignRev.get(state.state), state} + end + + def handle_call({:set_info, new_info}, _from, state) do + case SData.SignRev.set(state.state, new_info, state.pk) do + {:ok, st2} -> + Shard.Manager.save_state(state.id, st2) + state = put_in(state.state, st2) + bcast_state(state) + {:reply, :ok, state} + err -> + {:reply, err, state} + end + end + + def handle_cast(:init_pull, state) do + for {_, pid, _, _} <- Shard.Manager.list_peers do + GenServer.cast(pid, {:send_msg, {:interested, [state.id]}}) + end + {:noreply, state} + end + + def handle_cast({:interested, peer_id}, state) do + Shard.Manager.send(peer_id, {state.id, nil, {:update, SData.SignRev.signed(state.state)}}) + end + + def handle_cast({:msg, peer_id, _shard_id, nil, msg}, state) do + state = case msg do + {:update, signed} -> + case SData.SignRev.merge(state.state, signed, state.pk) do + {true, st2} -> + Shard.Manager.save_state(state.id, st2) + state = put_in(state.state, st2) + bcast_state(state, [peer_id]) + state + {false, _} -> + state + end + end + {:noreply, state} + end + + def bcast_state(state, exclude \\ []) do + for peer_id <- Shard.Manager.get_shard_peers(state.id) do + if not Enum.member? exclude, peer_id do + Shard.Manager.send(peer_id, {state.id, nil, {:update, SData.SignRev.signed(state.state)}}) + end + end + end +end diff --git a/shard/lib/application.ex b/shard/lib/application.ex index 3e3a6ac..9c1577a 100644 --- a/shard/lib/application.ex +++ b/shard/lib/application.ex @@ -15,7 +15,7 @@ defmodule Shard.Application do # Define workers and child supervisors to be supervised children = [ - Shard.Identity, + Shard.Keys, { DynamicSupervisor, strategy: :one_for_one, name: Shard.DynamicSupervisor }, # Networking diff --git a/shard/lib/cli/cli.ex b/shard/lib/cli/cli.ex index bf3a555..3778b2d 100644 --- a/shard/lib/cli/cli.ex +++ b/shard/lib/cli/cli.ex @@ -3,22 +3,45 @@ defmodule SCLI do Small command line interface for the chat application """ + defmodule State do + defstruct [:room_pid, :id_pid, :pk] + end + def run() do - for {_chid, _manifest, chpid} <- Shard.Manager.list_shards do + for {_chid, %SApp.Chat.Manifest{}, chpid} <- Shard.Manager.list_shards do GenServer.cast(chpid, {:subscribe, self()}) end - run(nil) + pk = case Shard.Keys.list_identities do + [pk1|_] -> pk1 + _ -> + IO.puts "Generating a new identity..." + Shard.Keys.new_identity + end + + run(%State{room_pid: nil, id_pid: nil, pk: pk}) end - defp run(pid) do + defp run(state) do handle_messages() - nick = Shard.Identity.get_nickname - prompt = case pid do + id_pid = case state.id_pid do + nil -> SApp.Identity.find_proc(state.pk) + x -> x + end + state = put_in(state.id_pid, id_pid) + + nick = case id_pid do + nil -> SApp.Identity.default_nick(state.pk) + _ -> + info = GenServer.call(id_pid, :get_info) + info.nick + end + + prompt = case state.room_pid do nil -> "(no channel) #{nick}: " _ -> - %SApp.Chat.Manifest{channel: chan} = GenServer.call(pid, :manifest) + %SApp.Chat.Manifest{channel: chan} = GenServer.call(state.room_pid, :manifest) "##{chan} #{nick}: " end @@ -28,13 +51,13 @@ defmodule SCLI do nil String.slice(str, 0..0) == "/" -> command = str |> String.slice(1..-1) |> String.split(" ") - pid2 = handle_command(pid, command) - run(pid2) + state = handle_command(state, command) + run(state) true -> if str != "" do - GenServer.cast(pid, {:chat_send, str}) + GenServer.cast(state.room_pid, {:chat_send, state.pk, str}) end - run(pid) + run(state) end end @@ -50,57 +73,71 @@ defmodule SCLI do end end - defp handle_command(pid, ["connect", ipstr, portstr]) do + defp handle_command(state, ["connect", ipstr, portstr]) do {:ok, ip} = :inet.parse_address (to_charlist ipstr) {port, _} = Integer.parse portstr Shard.Manager.add_peer(ip, port) - pid + state end - defp handle_command(pid, ["list"]) do + defp handle_command(state, ["list"]) do IO.puts "List of known channels:" - for {_chid, manifest, _chpid} <- Shard.Manager.list_shards do - %SApp.Chat.Manifest{channel: chan} = manifest + for {_chid, %SApp.Chat.Manifest{channel: chan}, _chpid} <- Shard.Manager.list_shards do IO.puts "##{chan}" end - pid + state end - defp handle_command(pid, ["hist"]) do - if pid == nil do + defp handle_command(state, ["hist"]) do + if state.room_pid == nil do IO.puts "Not currently on a channel!" else - GenServer.call(pid, {:read_history, nil, 25}) - |> Enum.each(fn {{ts, nick, msg}, true} -> - IO.puts "#{ts |> DateTime.from_unix! |> DateTime.to_iso8601} <#{nick}> #{msg}" + GenServer.call(state.room_pid, {:read_history, nil, 25}) + |> Enum.each(fn {{pk, msgbin, _sign}, true} -> + {ts, msg} = SData.term_unbin msgbin + nick = case SApp.Identity.find_proc pk do + nil -> + SApp.Identity.default_nick pk + pid -> + info = GenServer.call(pid, :get_info) + info.nick + end + IO.puts "#{ts |> DateTime.from_unix! |> DateTime.to_iso8601} <#{nick} #{pk|>binary_part(0, 4)|>Base.encode16|>String.downcase}> #{msg}" end) - pid end + state end - defp handle_command(_pid, ["join", qchan]) do - list = for {_chid, manifest, chpid} <- Shard.Manager.list_shards, - %SApp.Chat.Manifest{channel: chan} = manifest, - do: {chan, chpid} - case List.keyfind(list, qchan, 0) do + defp handle_command(state, ["join", qchan]) do + pid = SApp.Chat.find_proc qchan + case pid do nil -> - {:ok, pid} = DynamicSupervisor.start_child(Shard.DynamicSupervisor, {SApp.Chat, qchan}) + {:ok, pid} = Shard.Manifest.start %SApp.Chat.Manifest{channel: qchan} GenServer.cast(pid, {:subscribe, self()}) - pid - {_, pid} -> + %{state | room_pid: pid} + pid -> IO.puts "Switching to ##{qchan}" - pid + %{state | room_pid: pid} end end - defp handle_command(pid, ["nick", nick]) do - Shard.Identity.set_nickname nick - pid + defp handle_command(state, ["nick", nick]) do + pid = case state.id_pid do + nil -> SApp.Identity.find_proc state.pk + x -> x + end + if pid == nil do + IO.puts "Sorry, we have a problem with the identity shard" + else + info = GenServer.call(pid, :get_info) + GenServer.call(pid, {:set_info, %{info | nick: nick}}) + end + state end - defp handle_command(pid, _cmd) do + defp handle_command(state, _cmd) do IO.puts "Invalid command" - pid + state end end diff --git a/shard/lib/identity.ex b/shard/lib/identity.ex deleted file mode 100644 index 01b3c9e..0000000 --- a/shard/lib/identity.ex +++ /dev/null @@ -1,65 +0,0 @@ -defmodule Shard.Identity do - use Agent - require Salty.Sign.Ed25519, as: Sign - require Logger - - @identity_db [Application.get_env(:shard, :data_path), "identity_db"] |> Path.join |> String.to_atom - - def start_link(_) do - Agent.start_link(__MODULE__, :init, [], name: __MODULE__) - end - - def init() do - :dets.start - {:ok, @identity_db} = :dets.open_file @identity_db, type: :set - - case :dets.match @identity_db, :"$1" do - [] -> - Logger.info "Generating keypair..." - {pk, sk} = gen_keypair(Application.get_env(:shard, :peer_id_suffix)) - nick_suffix = pk - |> binary_part(0, 3) - |> Base.encode16 - |> String.downcase - nick = "Anon" <> nick_suffix - :dets.insert @identity_db, {pk, sk, nick} - %{ - keypair: {pk, sk}, - nickname: nick - } - [[{pk, sk, nick}] | _] -> - %{ - keypair: {pk, sk}, - nickname: nick - } - end - end - - defp gen_keypair(suffix, n \\ 0) do - {:ok, pk, sk} = Sign.keypair - if rem(n, 10000) == 0 do - Logger.info "#{n}... expected #{:math.pow(256, byte_size(suffix))}" - end - if :binary.longest_common_suffix([pk, suffix]) == byte_size(suffix) do - {pk, sk} - else - gen_keypair(suffix, n+1) - end - end - - def get_keypair() do - Agent.get(__MODULE__, &(&1.keypair)) - end - - def get_nickname() do - Agent.get(__MODULE__, &(&1.nickname)) - end - - def set_nickname(newnick) do - Agent.update(__MODULE__, fn state -> - {pk, sk} = state.keypair - :dets.insert @identity_db, {pk, sk, newnick} - %{state | nickname: newnick} - end) - end -end diff --git a/shard/lib/keys.ex b/shard/lib/keys.ex new file mode 100644 index 0000000..0dc3154 --- /dev/null +++ b/shard/lib/keys.ex @@ -0,0 +1,124 @@ +defmodule Shard.Keys do + @moduledoc""" + Module for saving private keys. + """ + + use Agent + require Salty.Sign.Ed25519, as: Sign + require Logger + + @key_db [Application.get_env(:shard, :data_path), "key_db"] |> Path.join |> String.to_atom + + def start_link(_) do + Agent.start_link(__MODULE__, :init, [], name: __MODULE__) + end + + def init() do + :dets.start + {:ok, @key_db} = :dets.open_file(@key_db, [type: :set]) + + case :dets.lookup(@key_db, :peer) do + [] -> + Logger.info "Generating peer keypair..." + {pk, sk} = gen_keypair(Application.get_env(:shard, :peer_id_suffix)) + :dets.insert @key_db, {:peer, pk, sk} + {pk, sk} + [{:peer, pk, sk}] -> + {pk, sk} + end + end + + defp gen_keypair(suffix, n \\ 0) do + {:ok, pk, sk} = Sign.keypair + if rem(n, 10000) == 0 do + Logger.info "#{n}... expected #{:math.pow(256, byte_size(suffix))}" + end + if check_suffix(pk, suffix) do + {pk, sk} + else + gen_keypair(suffix, n+1) + end + end + + defp check_suffix(pk, suffix) do + :binary.longest_common_suffix([pk, suffix]) == byte_size(suffix) + end + + def get_peer_keypair() do + Agent.get(__MODULE__, &(&1)) + end + + @doc""" + Generate a new keypair for a user identity, and start an Identity Shard for it. + """ + def new_identity() do + {pk, sk} = gen_keypair(Application.get_env(:shard, :identity_suffix)) + :dets.insert @key_db, {pk, sk} + SApp.Identity.start_link(pk) + pk + end + + @doc""" + List the public keys of all identities for which we have a secret key + """ + def list_identities() do + for [{pk, _sk}] <- :dets.match(@key_db, :"$1"), do: pk + end + + @doc""" + Lookup the secret key for a pk and sign a message with it. + + Returns the input value alongside its signature. + + Answer is {:ok, signed} if it worked, or :not_found if we didn't find the key. + """ + def sign(pk, bin) do + case :dets.lookup @key_db, pk do + [{^pk, sk}] -> + Sign.sign(bin, sk) + _ -> {:error, :not_found} + end + end + + @doc""" + Checks the signature appended to a signed message corresponds to a public key. + + If correct, returns {:ok, original_message} + """ + def open(pk, signed) do + if check_suffix(pk, Application.get_env(:shard, :identity_suffix)) do + Sign.open(signed, pk) + else + {:error, :invalid_pk_suffix} + end + end + + @doc""" + Lookup the secret key for a pk and generate a detached signature for a message. + + The original message is not returned. + + Answer is {:ok, signature} if it worked, or :not_found if we didn't find the key. + + """ + def sign_detached(pk, bin) do + case :dets.lookup @key_db, pk do + [{^pk, sk}] -> + Sign.sign_detached(bin, sk) + _ -> {:error, :not_found} + end + end + + @doc""" + Verify a detached signature for a message + + Returns :ok if the signature was correct. + """ + def verify(pk, bin, sign) do + if check_suffix(pk, Application.get_env(:shard, :identity_suffix)) do + Sign.verify_detached(sign, bin, pk) + else + {:error, :invalid_pk_suffix} + end + end +end diff --git a/shard/lib/manager.ex b/shard/lib/manager.ex index 57f2371..617378c 100644 --- a/shard/lib/manager.ex +++ b/shard/lib/manager.ex @@ -1,5 +1,17 @@ defprotocol Shard.Manifest do - @doc "Start the corresponding Shard process" + @moduledoc""" + A shard manifest is a data structure that uniquely defines the identity of the shard. + + The hash of the manifest is the unique identifier of that shard on the network. + + The Manifest protocol is a protocol implemented by the manifest structs for the + different shard types. It contains an operation start() that is able to launch the + correct process for this shard and connect to other peers that use it. + """ + + @doc""" + Start the corresponding Shard process + """ def start(manifest) end @@ -261,6 +273,16 @@ defmodule Shard.Manager do end @doc""" + Returns the pid for a shard if it exists + """ + def find_proc(shard_id) do + case :dets.lookup(@shard_db, shard_id) do + [{^shard_id, _, pid}] -> pid + _ -> nil + end + end + + @doc""" Register a process as the handler for shard packets for a given path. """ def dispatch_to(shard_id, path, pid) do diff --git a/shard/lib/net/tcpconn.ex b/shard/lib/net/tcpconn.ex index 35f7ea5..543341a 100644 --- a/shard/lib/net/tcpconn.ex +++ b/shard/lib/net/tcpconn.ex @@ -20,7 +20,7 @@ defmodule SNet.TCPConn do def handle_cast(:handshake, state) do socket = state.socket - {srv_pkey, srv_skey} = Shard.Identity.get_keypair + {srv_pkey, srv_skey} = Shard.Keys.get_peer_keypair {:ok, sess_pkey, sess_skey} = Box.keypair {:ok, challenge} = Salty.Random.buf 32 diff --git a/shard/mix.exs b/shard/mix.exs index 7192feb..14d0581 100644 --- a/shard/mix.exs +++ b/shard/mix.exs @@ -26,6 +26,7 @@ defmodule Shard.MixProject do defp deps do [ {:excoveralls, "~> 0.10", only: :test}, + {:ex_doc, "~> 0.19", only: :dev, runtime: false}, {:salty, "~> 0.1.3", hex: :libsalty}, ] diff --git a/shard/mix.lock b/shard/mix.lock index 4f92013..d9fe6a9 100644 --- a/shard/mix.lock +++ b/shard/mix.lock @@ -2,15 +2,20 @@ "certifi": {:hex, :certifi, "2.3.1", "d0f424232390bf47d82da8478022301c561cf6445b5b5fb6a84d49a9e76d2639", [:rebar3], [{:parse_trans, "3.2.0", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"}, "cowboy": {:hex, :cowboy, "1.1.2", "61ac29ea970389a88eca5a65601460162d370a70018afe6f949a29dca91f3bb0", [:rebar3], [{:cowlib, "~> 1.0.2", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.3.2", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"}, "cowlib": {:hex, :cowlib, "1.0.2", "9d769a1d062c9c3ac753096f868ca121e2730b9a377de23dec0f7e08b1df84ee", [:make], [], "hexpm"}, + "earmark": {:hex, :earmark, "1.2.6", "b6da42b3831458d3ecc57314dff3051b080b9b2be88c2e5aa41cd642a5b044ed", [:mix], [], "hexpm"}, "elixir_make": {:hex, :elixir_make, "0.4.2", "332c649d08c18bc1ecc73b1befc68c647136de4f340b548844efc796405743bf", [:mix], [], "hexpm"}, "ex2ms": {:hex, :ex2ms, "1.5.0", "19e27f9212be9a96093fed8cdfbef0a2b56c21237196d26760f11dfcfae58e97", [:mix], [], "hexpm"}, + "ex_doc": {:hex, :ex_doc, "0.19.1", "519bb9c19526ca51d326c060cb1778d4a9056b190086a8c6c115828eaccea6cf", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.7", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"}, "excoveralls": {:hex, :excoveralls, "0.10.0", "a4508bdd408829f38e7b2519f234b7fd5c83846099cda348efcb5291b081200c", [:mix], [{:hackney, "~> 1.13", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"}, "hackney": {:hex, :hackney, "1.13.0", "24edc8cd2b28e1c652593833862435c80661834f6c9344e84b6a2255e7aeef03", [:rebar3], [{:certifi, "2.3.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.1.2", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"}, "idna": {:hex, :idna, "5.1.2", "e21cb58a09f0228a9e0b95eaa1217f1bcfc31a1aaa6e1fdf2f53a33f7dbd9494", [:rebar3], [{:unicode_util_compat, "0.3.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"}, "jason": {:hex, :jason, "1.1.1", "d3ccb840dfb06f2f90a6d335b536dd074db748b3e7f5b11ab61d239506585eb2", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"}, + "makeup": {:hex, :makeup, "0.5.5", "9e08dfc45280c5684d771ad58159f718a7b5788596099bdfb0284597d368a882", [:mix], [{:nimble_parsec, "~> 0.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"}, + "makeup_elixir": {:hex, :makeup_elixir, "0.10.0", "0f09c2ddf352887a956d84f8f7e702111122ca32fbbc84c2f0569b8b65cbf7fa", [:mix], [{:makeup, "~> 0.5.5", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"}, "mime": {:hex, :mime, "1.3.0", "5e8d45a39e95c650900d03f897fbf99ae04f60ab1daa4a34c7a20a5151b7a5fe", [:mix], [], "hexpm"}, "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"}, + "nimble_parsec": {:hex, :nimble_parsec, "0.4.0", "ee261bb53214943679422be70f1658fff573c5d0b0a1ecd0f18738944f818efe", [:mix], [], "hexpm"}, "parse_trans": {:hex, :parse_trans, "3.2.0", "2adfa4daf80c14dc36f522cf190eb5c4ee3e28008fc6394397c16f62a26258c2", [:rebar3], [], "hexpm"}, "plug": {:hex, :plug, "1.3.6", "bcdf94ac0f4bc3b804bdbdbde37ebf598bd7ed2bfa5106ed1ab5984a09b7e75f", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1", [hex: :cowboy, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}], "hexpm"}, "ranch": {:hex, :ranch, "1.3.2", "e4965a144dc9fbe70e5c077c65e73c57165416a901bd02ea899cfd95aa890986", [:rebar3], [], "hexpm"}, diff --git a/shardweb/config/config.exs b/shardweb/config/config.exs index c496d1d..65f52bc 100644 --- a/shardweb/config/config.exs +++ b/shardweb/config/config.exs @@ -21,6 +21,7 @@ config :logger, :console, # Configuration for Shard itself (see shard/config/config.exs for expanations) config :shard, peer_id_suffix: "SH" +config :shard, identity_suffix: "ID" config :shard, data_path: Path.join [System.user_home, "shard", "data2"] # Import environment specific config. This must remain at the bottom |