Current section
Files
Jump to
Current section
Files
lib/merkle_fun.ex
defmodule MerkleFun do
require Integer
def new(input) do
tree = build_tree(input)
{tree, tuple_size(tree)}
end
def root({tree, _size}), do: bytes_to_string(elem(tree, 0))
def print({tree, _size}) do
tree
|> Tuple.to_list()
|> Enum.reject(fn x -> x == 1 end)
|> Enum.map(&bytes_to_string/1)
end
def proof({tree, _} = mt, leaf) do
leaf_hash =
leaf
|> Base.decode16!(case: :mixed)
|> hash()
idx =
tree
|> Tuple.to_list()
|> Enum.find_index(fn l -> l === leaf_hash end)
_proof(mt, idx)
|> Enum.map(&bytes_to_string/1)
|> Enum.map(&add_0x/1)
end
defp _proof(_tree, 0), do: []
defp _proof({tree, _len} = mt, idx) do
sibling_idx = get_sibling_idx(idx)
proof_node = elem(tree, sibling_idx)
parent_idx = Integer.floor_div(idx - 1, 2)
[proof_node | _proof(mt, parent_idx)]
end
defp build_tree(data) do
leaves =
data
|> Enum.map(fn x -> Base.decode16!(x, case: :mixed) end)
|> Enum.map(&hash/1)
|> Enum.sort()
tree = _build_tree(leaves, [])
{_, padded_tree} =
Enum.reduce(tree, {1, []}, fn row, {num, rows} ->
padded_row = pad_row(num, row)
{num * 2, [padded_row | rows]}
end)
padded_tree
|> Enum.reverse()
|> List.flatten()
|> List.to_tuple()
end
defp pad_row(num, row) do
true_len = length(row)
if num == 1 || true_len == num do
row
else
add_amount = num - true_len
# pad using 1, takes less space than nil
padding = List.duplicate(1, add_amount)
row ++ padding
end
end
defp _build_tree([root], acc), do: [[root] | acc]
defp _build_tree(level, acc) do
new_level =
level
|> Enum.chunk_every(2)
|> Enum.map(fn
[x] -> x
[x, y] -> combine(x, y)
end)
_build_tree(new_level, [level | acc])
end
defp combine(a, b) do
if(a == b) do
a
else
hash(a <> b)
end
end
defp hash(data), do: data |> ExKeccak.hash_256()
defp get_sibling_idx(0), do: 0
defp get_sibling_idx(idx) do
if Integer.is_even(idx) do
idx - 1
else
idx + 1
end
end
defp bytes_to_string(bytes), do: Base.encode16(bytes, case: :lower)
defp add_0x(s), do: "0x#{s}"
end