diff --git a/src/eblake2.erl b/src/eblake2.erl index 7f63a25..d277fd8 100644 --- a/src/eblake2.erl +++ b/src/eblake2.erl @@ -9,8 +9,11 @@ %% API exports -export([ blake2b/2 , blake2b/3 + , blake2s/2 + , blake2s/3 ]). +-define(MAXINT_32, 16#FFFFffff). -define(MAXINT_64, 16#FFFFffffFFFFffff). %%==================================================================== @@ -27,19 +30,42 @@ blake2b(HashLen, Msg0, Key) -> %% Set up the initial state Init = (16#01010000 + (byte_size(Key) bsl 8) + HashLen), - <> = blake_iv(), + <> = blake2b_iv(), H = <<(H0 bxor Init):64, H1_7/binary>>, %% Perform the compression - message will be chopped into 128-byte chunks. State = blake2b_compress(H, Msg, 0), %% Just return the requested part of the hash - {ok, binary_part(to_little_endian(State), {0, HashLen})}. + {ok, binary_part(to_little_endian(64, State), {0, HashLen})}. + +-spec blake2s(HashLen :: integer(), Msg :: binary()) -> {ok, binary()}. +blake2s(HashLen, Msg) -> + blake2s(HashLen, Msg, <<>>). + +-spec blake2s(HashLen :: integer(), Msg :: binary(), Key :: binary()) -> {ok, binary()}. +blake2s(HashLen, Msg0, Key) -> + %% If message should be keyed, prepend message with padded key. + Msg = <<(pad(64, Key))/binary, Msg0/binary>>, + + %% Set up the initial state + Init = (16#01010000 + (byte_size(Key) bsl 8) + HashLen), + <> = blake2s_iv(), + H = <<(H0 bxor Init):32, H1_7/binary>>, + + %% Perform the compression - message will be chopped into 64-byte chunks. + State = blake2s_compress(H, Msg, 0), + + %% Just return the requested part of the hash + {ok, binary_part(to_little_endian(32, State), {0, HashLen})}. + %%==================================================================== %% Internal functions %%==================================================================== %% + +%% Blake2b blake2b_compress(H, <>, BCompr) when Rest /= <<>> -> H1 = blake2b_compress(H, <>, BCompr + 128, false), blake2b_compress(H1, Rest, BCompr + 128); @@ -49,8 +75,8 @@ blake2b_compress(H, SmallChunk, BCompr) -> blake2b_compress(H, <>, BCompr + Size, true). blake2b_compress(H, Chunk0, BCompr, Last) -> - Chunk = to_big_endian(Chunk0), - <> = <>, + Chunk = to_big_endian(64, Chunk0), + <> = <>, V12_ = V12 bxor (BCompr band ?MAXINT_64), V13_ = V13 bxor ((BCompr bsr 64) band ?MAXINT_64), V14_ = case Last of @@ -103,7 +129,7 @@ blake2b_mix(Va, Vb, Vc, Vd, X, Y) -> {Va2, Vb2, Vc2, Vd2}. -blake_iv() -> +blake2b_iv() -> IV0 = 16#6A09E667F3BCC908, IV1 = 16#BB67AE8584CAA73B, IV2 = 16#3C6EF372FE94F82B, @@ -138,19 +164,102 @@ rotr641(24, <>) -> <>; rotr641(32, <>) -> <>; rotr641(63, <>) -> <>. + +%% Blake2s +blake2s_iv() -> + IV0 = 16#6A09E667, IV1 = 16#BB67AE85, + IV2 = 16#3C6EF372, IV3 = 16#A54FF53A, + IV4 = 16#510E527F, IV5 = 16#9B05688C, + IV6 = 16#1F83D9AB, IV7 = 16#5BE0CD19, + <>. + +blake2s_compress(H, <>, BCompr) when Rest /= <<>> -> + H1 = blake2s_compress(H, <>, BCompr + 64, false), + blake2s_compress(H1, Rest, BCompr + 64); +blake2s_compress(H, SmallChunk, BCompr) -> + Size = byte_size(SmallChunk), + FillSize = (64 - Size) * 8, + blake2s_compress(H, <>, BCompr + Size, true). + +blake2s_compress(H, Chunk0, BCompr, Last) -> + Chunk = to_big_endian(32, Chunk0), + <> = <>, + V12_ = V12 bxor (BCompr band ?MAXINT_32), + V13_ = V13 bxor ((BCompr bsr 32) band ?MAXINT_32), + V14_ = case Last of + false -> V14; + true -> V14 bxor ?MAXINT_32 + end, + V = <>, + + <> = + lists:foldl(fun(Round, Vx) -> blake2s_mix(Round, Chunk, Vx) end, V, lists:seq(0, 9)), + + <> = H, + <<((HInt bxor VLow) bxor VHigh):(8*32)>>. + +blake2s_mix(Rnd, Chunk, V) -> + <> = V, + <> = Chunk, + Ms = {M0, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12, M13, M14, M15}, + M = fun(Ix) -> element(Ix+1, Ms) end, + + [S0, S1, S2, S3, S4, S5, S6, S7, S8, S9, S10, S11, S12, S13, S14, S15] = sigma(Rnd rem 10), + + {Vx0, Vx4, Vx8, Vx12} = blake2s_mix(V0, V4, V8, V12, M(S0), M(S1)), + {Vx1, Vx5, Vx9, Vx13} = blake2s_mix(V1, V5, V9, V13, M(S2), M(S3)), + {Vx2, Vx6, Vx10, Vx14} = blake2s_mix(V2, V6, V10, V14, M(S4), M(S5)), + {Vx3, Vx7, Vx11, Vx15} = blake2s_mix(V3, V7, V11, V15, M(S6), M(S7)), + + {Vy0, Vy5, Vy10, Vy15} = blake2s_mix(Vx0, Vx5, Vx10, Vx15, M(S8), M(S9)), + {Vy1, Vy6, Vy11, Vy12} = blake2s_mix(Vx1, Vx6, Vx11, Vx12, M(S10), M(S11)), + {Vy2, Vy7, Vy8, Vy13} = blake2s_mix(Vx2, Vx7, Vx8, Vx13, M(S12), M(S13)), + {Vy3, Vy4, Vy9, Vy14} = blake2s_mix(Vx3, Vx4, Vx9, Vx14, M(S14), M(S15)), + + <>. + +blake2s_mix(Va, Vb, Vc, Vd, X, Y) -> + Va1 = (Va + Vb + X) band ?MAXINT_32, + Vd1 = rotr32(16, Vd bxor Va1), + + Vc1 = (Vc + Vd1) band ?MAXINT_32, + Vb1 = rotr32(12, Vb bxor Vc1), + + Va2 = (Va1 + Vb1 + Y) band ?MAXINT_32, + Vd2 = rotr32(8, Va2 bxor Vd1), + + Vc2 = (Vc1 + Vd2) band ?MAXINT_32, + Vb2 = rotr32(7, Vb1 bxor Vc2), + + {Va2, Vb2, Vc2, Vd2}. + +rotr32(N, I32) -> + <> = rotr321(N, <>), + I32rot. + +rotr321(16, <>) -> <>; +rotr321(12, <>) -> <>; +rotr321(8, <>) -> <>; +rotr321(7, <>) -> <>. + pad(N, Bin) -> case (N - (byte_size(Bin) rem N)) rem N of 0 -> Bin; Pad -> <> end. -to_big_endian(Bin) -> to_big_endian(Bin, <<>>). -to_big_endian(<<>>, Acc) -> Acc; -to_big_endian(<>, Acc) -> - to_big_endian(Rest, <>). +to_big_endian(X, Bin) -> to_big_endian(X, Bin, <<>>). +to_big_endian(_X, <<>>, Acc) -> Acc; +to_big_endian(X, Bin, Acc) -> + <> = Bin, + to_big_endian(X, Rest, <>). -to_little_endian(Bin) -> to_little_endian(Bin, <<>>). -to_little_endian(<<>>, Acc) -> Acc; -to_little_endian(<>, Acc) -> - to_little_endian(Rest, <>). +to_little_endian(X, Bin) -> to_little_endian(X, Bin, <<>>). +to_little_endian(_X, <<>>, Acc) -> Acc; +to_little_endian(X, Bin, Acc) -> + <> = Bin, + to_little_endian(X, Rest, <>). diff --git a/test/eblake2_tests.erl b/test/eblake2_tests.erl index e09826c..8070aa2 100644 --- a/test/eblake2_tests.erl +++ b/test/eblake2_tests.erl @@ -17,6 +17,13 @@ blake2b_test_() -> blake2b(_TC = #{in := Msg, key := Key, out := ExpectedOut}) -> ?assertEqual(eblake2:blake2b(byte_size(ExpectedOut), Msg, Key), {ok, ExpectedOut}). +blake2s_test_() -> + {"Tests for BLAKE2s hash implementation", + [ fun() -> blake2s(TC) end || TC <- filter_test_vectors(<<"blake2s">>) ]}. + +blake2s(_TC = #{in := Msg, key := Key, out := ExpectedOut}) -> + ?assertEqual(eblake2:blake2s(byte_size(ExpectedOut), Msg, Key), {ok, ExpectedOut}). + %% Helper functions test_vectors() -> parse_test_vectors("test/blake2_testvectors.json").