Implement Shamir secret sharing and recovery.

This commit is contained in:
Robert Newson 2011-08-14 23:15:44 +01:00
commit dab8df7189
7 changed files with 228 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
ebin
*~

11
Makefile Normal file
View File

@ -0,0 +1,11 @@
ERL ?= erl
APP := shamir
all:
@./rebar compile
clean:
@./rebar clean
test: all
@(./rebar skip_deps=true eunit)

BIN
rebar vendored Executable file

Binary file not shown.

83
src/galois.erl Normal file
View File

@ -0,0 +1,83 @@
%% Copyright 2011 Robert Newson
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
-module(galois).
-export([generate/1, add/3, subtract/3, multiply/3, divide/3]).
-define(nw(W), (1 bsl W)).
-record(galois, {
w,
gflog,
gfilog
}).
generate(W) ->
generate(W, 1, 0).
generate(W, B, Log) ->
generate(W, B, Log, dict:new(), dict:new()).
generate(W, _B, Log, Gflog, Gfilog) when Log =:= ?nw(W) -1 ->
#galois{w=W, gflog=Gflog, gfilog=Gfilog};
generate(W, B, Log, Gflog, Gfilog) ->
Gflog1 = dict:store(B, Log, Gflog),
Gfilog1 = dict:store(Log, B, Gfilog),
B1 = B bsl 1,
B2 = if
B1 band ?nw(W) > 0 ->
B1 bxor prim_poly(W);
true ->
B1
end,
generate(W, B2, Log + 1, Gflog1, Gfilog1).
multiply(#galois{}, 0, _) ->
0;
multiply(#galois{}, _, 0) ->
0;
multiply(#galois{w=W, gflog=Gflog, gfilog=Gfilog}, A, B) ->
case dict:fetch(A, Gflog) + dict:fetch(B, Gflog) of
SumLog when SumLog >= ?nw(W) - 1 ->
dict:fetch(SumLog - (?nw(W) - 1), Gfilog);
SumLog ->
dict:fetch(SumLog, Gfilog)
end.
divide(#galois{}, 0, _) ->
0;
divide(#galois{}, _, 0) ->
throw(division_by_zero);
divide(#galois{w=W, gflog=Gflog, gfilog=Gfilog}, A, B) ->
case dict:fetch(A, Gflog) - dict:fetch(B, Gflog) of
DiffLog when DiffLog < 0 ->
dict:fetch(DiffLog + (?nw(W) - 1), Gfilog);
DiffLog ->
dict:fetch(DiffLog, Gfilog)
end.
add(#galois{}, A, B) ->
A bxor B.
subtract(#galois{}, A, B) ->
A bxor B.
prim_poly(4) ->
8#23;
prim_poly(8) ->
8#435;
prim_poly(16) ->
8#210013.

29
src/shamir.app.src Normal file
View File

@ -0,0 +1,29 @@
%% Copyright 2011 Robert Newson
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%-*- mode: erlang -*-
{application, shamir,
[
{description, "shamir"},
{vsn, git},
{modules, []},
{registered, []},
{applications, [
kernel,
stdlib,
crypto
]},
{mod, { shamir, []}},
{env, []}
]}.

81
src/shamir.erl Normal file
View File

@ -0,0 +1,81 @@
%% Copyright 2011 Robert Newson
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
-module(shamir).
-export([share/3, recover/1]).
-record(share, {
threshold,
x,
y
}).
share(Secret, Threshold, Count) when is_binary(Secret) ->
share(binary_to_list(Secret), Threshold, Count);
share(Secret, Threshold, Count) when is_list(Secret) ->
Shares = transpose([share(Byte, Threshold, Count) || Byte <- Secret]),
[#share{threshold=Threshold, x=X, y=list_to_binary(lists:nth(X, Shares))}
|| X <- lists:seq(1, Count)];
share(Secret, Threshold, Count) when (Secret >= 0 andalso Secret =< 255) ->
GF = galois:generate(8),
Coeffs = binary_to_list(crypto:rand_bytes(Threshold - 1)) ++ [Secret],
[horner(GF, X, Coeffs) || X <- lists:seq(1, Count)].
horner(GF, X, Coeffs) ->
horner(GF, X, Coeffs, 0).
horner(_, _, [], Acc) ->
Acc;
horner(GF, X, [Coeff|Rest], Acc) ->
Mult = galois:multiply(GF, Acc, X),
Add = galois:add(GF, Mult, Coeff),
horner(GF, X, Rest, Add).
recover([#share{threshold=Threshold}|_]=Shares0) ->
Shares = lists:ukeysort(#share.x, Shares0),
X = [X || #share{x=X} <- Shares],
Ys = transpose(lists:map(fun(#share{y=Y}) ->
binary_to_list(Y) end, Shares)),
list_to_binary([recover(Threshold, Z) || Z <- [lists:zip(X, Y) || Y <- Ys]]).
recover(Threshold, Shares) when length(Shares) >= Threshold ->
lagrange(lists:sublist(Shares, Threshold)).
lagrange(Shares) ->
GF = galois:generate(8),
lists:foldl(fun(Share, Acc) ->
galois:add(GF, lagrange(GF, Share, Shares), Acc) end,
0, Shares).
lagrange(GF, Share, Shares) ->
lagrange(GF, Share, Shares, 1).
lagrange(GF, {_, Y}, [], Acc) ->
galois:multiply(GF, Y, Acc);
lagrange(GF, {X, Y}, [{X, _} | Rest], Acc) ->
lagrange(GF, {X, Y}, Rest, Acc);
lagrange(GF, {X1, Y1}, [{X2, _} | Rest], Acc) ->
lagrange(GF, {X1, Y1}, Rest,
galois:multiply(GF, Acc,
galois:divide(GF, X2,
galois:subtract(GF, X1, X2)))).
transpose([[X | Xs] | Xss]) ->
[[X | [H || [H | _] <- Xss]]
| transpose([Xs | [T || [_ | T] <- Xss]])];
transpose([[] | Xss]) -> transpose(Xss);
transpose([]) -> [].

22
test/shamir_test.erl Normal file
View File

@ -0,0 +1,22 @@
%% Copyright 2011 Robert Newson
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
-module(shamir_test).
-include_lib("eunit/include/eunit.hrl").
shamir_test() ->
Secret = <<"hello">>,
Shares = shamir:share(Secret, 3, 4),
RecoveredSecret= shamir:recover(Shares),
?assertEqual(Secret, RecoveredSecret).