diff --git a/lib/shamir/shamir.ex b/lib/shamir/shamir.ex index ee7ce33..efc2a9c 100644 --- a/lib/shamir/shamir.ex +++ b/lib/shamir/shamir.ex @@ -2,73 +2,79 @@ defmodule KeyX.Shamir do alias KeyX.Shamir.Arithmetic @spec split_secret(non_neg_integer, non_neg_integer, binary) :: list(binary) - def split_secret(_k, n, _secret) when n > 255, do: raise "too many parts, n <= 255" - def split_secret(k, n, _secret) when k > n, do: raise "k cannot be less than total shares" - def split_secret(k, _n, _secret) when k < 2, do: raise "k cannot be less than 2" - def split_secret(_k, _n, secret) when length(secret) == 0, do: raise "secret cannot be zero" + def split_secret(_k, n, _secret) when n > 255, do: raise("too many parts, n <= 255") + def split_secret(k, n, _secret) when k > n, do: raise("k cannot be less than total shares") + def split_secret(k, _n, _secret) when k < 2, do: raise("k cannot be less than 2") + def split_secret(_k, _n, secret) when length(secret) == 0, do: raise("secret cannot be zero") + def split_secret(k, n, secret) do - set_random_seed() # Generate random x coordinates + # Generate random x coordinates + set_random_seed() x_coorinates = 0..254 |> rand_shuffle # This is where implementations can differ, presumably. The H.C. Vault developers noted: # // Make random polynomials for each byte of the secret # // Construct a random polynomial for each byte of the secret. - # // Because we are using a field of size 256, we can only represent - # // a single byte as the intercept of the polynomial, so we must - # // use a new polynomial for each byte. + # // Because we are using a field of size 256, we can only represent + # // a single byte as the intercept of the polynomial, so we must + # // use a new polynomial for each byte. # We could use larger numbers (large field set (larger primes?)), # but maintaining compatibility is a key goal in this project shares_init = for _ <- 1..n, do: [] - shares = Enum.reduce :binary.bin_to_list(secret), shares_init, fn(val, shares) -> - poly = Arithmetic.polynomial(val, k-1) + shares = + Enum.reduce(:binary.bin_to_list(secret), shares_init, fn val, shares -> + poly = Arithmetic.polynomial(val, k - 1) - for {x_val,y_acc} <- Enum.zip(x_coorinates, shares), into: [] do - x = x_val + 1 - y = poly |> Arithmetic.evaluate(x) - [ y_acc, y ] - end - end + for {x_val, y_acc} <- Enum.zip(x_coorinates, shares), into: [] do + x = x_val + 1 + y = poly |> Arithmetic.evaluate(x) + [y_acc, y] + end + end) - for {share,x} <- Enum.zip(shares, x_coorinates), into: [] do - :binary.list_to_bin([share, (x + 1) ]) + for {share, x} <- Enum.zip(shares, x_coorinates), into: [] do + :binary.list_to_bin([share, x + 1]) end end - @spec recover_secret( list(binary) ) :: binary + @spec recover_secret(list(binary)) :: binary def recover_secret(shares) do # Constants shares = Enum.map(shares, &:binary.bin_to_list/1) sizes = for share <- shares, into: [], do: length(share) - [ size | other_sz ] = sizes |> Enum.uniq + [size | other_sz] = sizes |> Enum.uniq() y_len = size - 1 x_samples = for share <- shares, do: List.last(share) # Error checking - unless [] = other_sz, do: raise "shares must match in size" - unless length(x_samples) == MapSet.size(MapSet.new(x_samples)), do: raise "Duplicated shares" + unless [] == other_sz, do: raise("shares must match in size") + unless length(x_samples) == MapSet.size(MapSet.new(x_samples)), do: raise("Duplicated shares") # Evaluate polynomials and return secret! - res = for idx <- 0..(y_len-1), into: [] do - y_samples = for share <- shares, into: [] do - share |> Enum.at(idx) + res = + for idx <- 0..(y_len - 1), into: [] do + y_samples = + for share <- shares, into: [] do + share |> Enum.at(idx) + end + + KeyX.Shamir.Arithmetic.interpolate(x_samples, y_samples, 0) end - KeyX.Shamir.Arithmetic.interpolate(x_samples, y_samples, 0) - end - res |> :binary.list_to_bin + res |> :binary.list_to_bin() end - def set_random_seed() do # https://hashrocket.com/blog/posts/the-adventures-of-generating-random-numbers-in-erlang-and-elixir - << i1 :: unsigned-integer-32, i2 :: unsigned-integer-32, i3 :: unsigned-integer-32>> = :crypto.strong_rand_bytes(12) + <> = + :crypto.strong_rand_bytes(12) + :rand.seed(:exsplus, {i1, i2, i3}) end def rand_shuffle(list) do - list |> Enum.sort_by( fn _x -> :rand.uniform() end ) + list |> Enum.sort_by(fn _x -> :rand.uniform() end) end - end diff --git a/lib/shamir/shamir_math.ex b/lib/shamir/shamir_math.ex index 61b87ac..363067b 100644 --- a/lib/shamir/shamir_math.ex +++ b/lib/shamir/shamir_math.ex @@ -1,6 +1,5 @@ defmodule KeyX.Shamir.Arithmetic do import Kernel, except: [+: 2, *: 2, /: 2] - import Bitwise import Enum, only: [at: 2] alias KeyX.Shamir.Tables @@ -9,64 +8,73 @@ defmodule KeyX.Shamir.Arithmetic do @spec polynomial(non_neg_integer, non_neg_integer) :: polynomial def polynomial(intercept, degree) do - [ intercept | (:crypto.strong_rand_bytes(degree) |> :binary.bin_to_list()) ] + [intercept | :crypto.strong_rand_bytes(degree) |> :binary.bin_to_list()] end @spec evaluate(polynomial, non_neg_integer) :: non_neg_integer def evaluate(poly, x) when x === 0 do poly |> at(0) end + def evaluate(poly, x) do - [ poly_tail | poly_rest_rev ] = Enum.reverse(poly) + [poly_tail | poly_rest_rev] = Enum.reverse(poly) - Enum.reduce(poly_rest_rev, poly_tail, fn(poly_coef, acc) -> - (acc * x) + poly_coef + Enum.reduce(poly_rest_rev, poly_tail, fn poly_coef, acc -> + acc * x + poly_coef end) end def interpolate(x_samples, y_samples, x) - when length(x_samples) == length(x_samples) do + when length(x_samples) == length(x_samples) do # Setup interpolation env limit = length(x_samples) - 1 # Loop through all the x & y samples, reducing them to an answer - Enum.reduce 0..limit, 0, fn(i, result) -> + Enum.reduce(0..limit, 0, fn i, result -> # skip i == j on inner reduce inner_rng = Enum.reject(0..limit, &(&1 == i)) - basis = Enum.reduce inner_rng, 1, fn(j, basis) -> - basis * ( (x + at(x_samples, j) ) - / (at(x_samples, i) + at(x_samples, j)) ) - end + basis = + Enum.reduce(inner_rng, 1, fn j, basis -> + basis * + ((x + at(x_samples, j)) / + (at(x_samples, i) + at(x_samples, j))) + end) + group = basis * at(y_samples, i) result + group - end + end) end - def interpolate(_x_samples, _y_samples, _x), do: raise "Invalid arguments" - def _lhs / rhs when rhs === 0, do: raise ArithmeticError + def interpolate(_x_samples, _y_samples, _x), do: raise("Invalid arguments") + + def _lhs / rhs when rhs === 0, do: raise(ArithmeticError) + def lhs / rhs do zero = 0 - ret = Kernel.-(Tables.log(lhs), Tables.log(rhs)) + + ret = + Kernel.-(Tables.log(lhs), Tables.log(rhs)) |> Kernel.+(255) |> rem(255) - |> Tables.exp + |> Tables.exp() - if (lhs ===0), do: zero, else: ret + if lhs === 0, do: zero, else: ret end # Multiplies two numbers in GF(2^8) def lhs * rhs do zero = 0 - ret = Kernel.+(Tables.log(lhs), Tables.log(rhs)) + + ret = + Kernel.+(Tables.log(lhs), Tables.log(rhs)) |> rem(255) - |> Tables.exp + |> Tables.exp() if :erlang.or(lhs === 0, rhs === 0), do: zero, else: ret end # @spec evaluate(polynomial, non_neg_integer) :: non_neg_integer - def lhs + rhs, do: lhs ^^^ rhs - - + # lhs ^^^ rhs + def lhs + rhs, do: Bitwise.bxor(lhs, rhs) end