Files
kek/kek.erl
T

1019 lines
31 KiB
Erlang

%% @doc
%% References
%% 1. Helpful lecture: https://www.youtube.com/watch?v=JWskjzgiIa4
%% 2. NIST standard: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.202.pdf
%% (btw: the double bar notation means "concatenate")
%% 3. https://en.wikipedia.org/wiki/SHA-3
%% @end
-module(kek).
% theta and rho steps are done
-compile(export_all).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% TOP LEVEL API
%%%
%%% sha*s and shake*s
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-spec sha3_224(Message) -> Digest
when Message :: bitstring(),
Digest :: <<_:224>>.
%% @doc
%% SHA-3 with an output bit length of 224 bits.
%% @end
sha3_224(Message) ->
sha3(224, Message).
-spec sha3_256(Message) -> Digest
when Message :: bitstring(),
Digest :: <<_:256>>.
%% @doc
%% SHA-3 with an output bit length of 256 bits.
%% @end
sha3_256(Message) ->
sha3(256, Message).
-spec sha3_384(Message) -> Digest
when Message :: bitstring(),
Digest :: <<_:384>>.
%% @doc
%% SHA-3 with an output bit length of 384 bits.
%% @end
sha3_384(Message) ->
sha3(384, Message).
-spec sha3_512(Message) -> Digest
when Message :: bitstring(),
Digest :: <<_:512>>.
%% @doc
%% SHA-3 with an output bit length of 512 bits.
%% @end
sha3_512(Message) ->
sha3(512, Message).
-spec sha3(OutputBitLength, Message) -> Digest
when OutputBitLength :: pos_integer(),
Message :: bitstring(),
Digest :: bitstring().
%% @doc
%% SHA-3 with an arbitrary output bit length.
%%
%% This means Keccak with Capacity = 2*OutputBitLength. Additionally, SHA3
%% concatenates the bits 01 onto the end of the input, before sending the
%% Message to keccak/3.
%% @end
sha3(OutputBitLength, Message) ->
Capacity = 2*OutputBitLength,
ShaMessage = <<Message/bitstring, (2#01):2>>,
keccak(Capacity, ShaMessage, OutputBitLength).
-spec shake128(Message, OutputBitLength) -> Digest
when Message :: bitstring(),
OutputBitLength :: pos_integer(),
Digest :: bitstring().
%% @doc
%% This is the SHAKE variable-length hash with Capacity 256 = 2*128 bits.
%% @end
shake128(Message, OutputBitLength) ->
shake(128, Message, OutputBitLength).
-spec shake256(Message, OutputBitLength) -> Digest
when Message :: bitstring(),
OutputBitLength :: pos_integer(),
Digest :: bitstring().
%% @doc
%% This is the SHAKE variable-length hash with Capacity 512 = 2*256 bits.
%% @end
shake256(Message, OutputBitLength) ->
shake(256, Message, OutputBitLength).
-spec shake(ShakeNumber, Message, OutputBitLength) -> Digest
when ShakeNumber :: pos_integer(),
Message :: bitstring(),
OutputBitLength :: pos_integer(),
Digest :: bitstring().
%% @doc
%% This is the SHAKE variable-length hash with Capacity 512 = 2*ShakeNumber bits.
%%
%% This concatenates the bitstring 1111 onto the end of the Message before
%% sending the message to keccak/3.
%% @end
shake(ShakeNumber, Message, OutputBitLength) ->
Capacity = 2*ShakeNumber,
ShakeMessage = <<Message/bitstring, (2#1111):4>>,
keccak(Capacity, ShakeMessage, OutputBitLength).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% OUTER KECCAK
%%%
%%% Keccak pads the input, absorbs it into the sponge, and squeezes the bits out
%%% of the sponge. The absorption and squeezing phases invoke "inner keccak",
%%% which is the heart of the algorithm.
%%%
%%% - keccak/3
%%% - pad/2
%%% - absorb/4
%%% - squeeze/3
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-spec keccak(Capacity, Message, OutputBitLength) -> Digest
when Capacity :: pos_integer(),
Message :: bitstring(),
OutputBitLength :: pos_integer(),
Digest :: bitstring().
%% @doc
%% Note: this is Keccak 1600, the only one used in practice
%%
%% Capacity must be strictly less than 1600
%% @end
keccak(Capacity = _c, Message, OutputBitLength) ->
BitRate = 1600 - Capacity,
PaddedMessage = pad(Message, BitRate),
InitialSponge = <<0:1600>>,
WetSponge = absorb(PaddedMessage, BitRate, Capacity, InitialSponge),
ResultBits = squeeze(WetSponge, OutputBitLength, BitRate),
ResultBits.
-spec pad(Message, BitRate) -> NewMessage
when Message :: bitstring(),
BitRate :: pos_integer(),
NewMessage :: bitstring().
%% @private
%% padding
%% divide the message into r-bit blocks
%%
%% the message ends with 1000...0001
%%
%% sha3 calls this /10*1/ as in the regex
%%
%% Reference: https://en.wikipedia.org/wiki/SHA-3#Padding
%% @end
% note: rem will always return a positive integer because both bit_size
% case when the message bit length is evenly divisible by the bit rate
% in this case we add a whole new r-word
pad(Message, BitRate = _r) when (bit_size(Message) rem BitRate) =:= 0 ->
% Suppose the BitRate was 8 and we had 0 bits left
% Input:
% Bits: <<>>
% Idx1: 12345678
% Result:
% Bits: 10000001
% Idx1: 12345678
% In this case we add a new r-word
NewRWord = <<1:1, 0:(BitRate - 2), 1:1>>,
NewMessage = <<Message/bitstring, NewRWord/bitstring>>,
NewMessage;
% this is the retarded case, when the bit length of the message is exactly one
% bit less than dividing the BitRate
pad(Message, BitRate = _r) when (bit_size(Message) rem BitRate) =:= (BitRate - 1) ->
% Suppose the BitRate was 8 and we had 7 bits left
% Input:
% Bits: ABCDEFG
% Idx1: 12345678
% Result:
% Bits: ABCDEFG1 00000001
% Idx1: 12345678 12345678
% in this case, we add a 1, (r-1) zeros, and a trailing 1
NewRWord = <<1:1, 0:(BitRate - 1), 1:1>>,
NewMessage = <<Message/bitstring, NewRWord/bitstring>>,
NewMessage;
% this is the general case, where there are at least 2 bits left in order to
% fill out the r-word
pad(Message, BitRate = _r) ->
% Suppose the BitRate was 8 and we had 3 bits left
% Input:
% Bits: ABC
% Idx1: 12345678
% Result:
% Bits: ABC10001
% Idx1: 12345678
NumberOfMessageBitsInTheLastRWord = bit_size(Message) rem BitRate,
NumberOfNewBitsNeeded = BitRate - NumberOfMessageBitsInTheLastRWord,
NumberOfNewZerosNeeded = NumberOfNewBitsNeeded - 2,
NewMessage = <<Message/bitstring, 1:1, 0:NumberOfNewZerosNeeded, 1:1>>,
NewMessage.
-spec absorb(PaddedMessage, BitRate, Capacity, SpongeAcc) -> WetSponge
when PaddedMessage :: bitstring(),
BitRate :: pos_integer(),
Capacity :: pos_integer(),
SpongeAcc :: <<_:1600>>,
WetSponge :: <<_:1600>>.
%% @private
%% Assumptions:
%% 1. BitRate + Capacity = 1600,
%% 2. BitRate divides the PaddedMessage length (i.e. already have done padding)
%% @end
% can pull off r bits from the start of the message
absorb(PaddedMessageBits, BitRate = _r, Capacity = _c, Sponge) when BitRate =< bit_size(PaddedMessageBits) ->
<<ThisRWord:BitRate, Rest/bitstring>> = PaddedMessageBits,
% we bitwise xor the sponge against the r word followed by a bunch of 0s
<<SpongeInt:1600>> = Sponge,
<<Foo:1600>> = <<ThisRWord:BitRate, 0:Capacity>>,
FInputInt = SpongeInt bxor Foo,
FInputBits = <<FInputInt:1600>>,
NewSponge = inner_keccak(FInputBits),
absorb(Rest, BitRate, Capacity, NewSponge);
% empty string, return the sponge
absorb(<<>>, _r, _c, FinalSponge) ->
FinalSponge.
-spec squeeze(WetSponge, OutputBitLength, BitRate) -> ResultBits
when WetSponge :: <<_:1600>>,
OutputBitLength :: pos_integer(),
BitRate :: pos_integer(),
ResultBits :: bitstring().
%% @private
%% squeeze the output bits out of the sponge
%% @end
%%% % simple case: bit length is less than (or equal to) the sponge size, just grab
%%% % the first ones
%%% % this is the case for the shas
%%% squeeze(<<ResultBits:OutputBitLength, _Rest/bitstring>>, OutputBitLength, _BitRate) ->
%%% <<ResultBits:OutputBitLength>>;
% general case: output bit length is greater than the sponge size, construct
% accumulatively
% this is the case for the variable-length encodings
squeeze(WetSponge, OutputBitLength, BitRate) ->
InitOutputAcc = <<>>,
really_squeeze(WetSponge, OutputBitLength, BitRate, InitOutputAcc).
% terminal case: we have enough bits in the output, return those
really_squeeze(_WetSponge, OutputBitLength, _BitRate, FinalAccBits) when OutputBitLength =< bit_size(FinalAccBits) ->
<<ResultBits:OutputBitLength, _/bitstring>> = FinalAccBits,
<<ResultBits:OutputBitLength>>;
% general case: need moar bits
% in this case
% - we grab the first r bits of the sponge, add them to the accumulator
% - re-kek the sponge
% - try again
really_squeeze(WetSponge, OutputBitLength, BitRate, ResultAcc)->
<<ThisRWord:BitRate, _/bitstring>> = WetSponge,
NewResultAcc = <<ResultAcc/bitstring, ThisRWord:BitRate>>,
NewWetSponge = inner_keccak(WetSponge),
really_squeeze(NewWetSponge, OutputBitLength, BitRate, NewResultAcc).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% THE DREADED INNER KECCAK
%%%
%%% This is the "f" function that appears in all the documentation.
%%%
%%% The input is the 1600-bit sponge array. inner_keccak/1 sends the input
%%% through 24 "rounds". Each round consists of the 5 Greek letter steps, each of
%%% which is a weird transformation on the array.
%%%
%%% In "inner keccak", the input array is thought of as a 5x5x64 3D array. The
%%% coordinate system is described in its own section.
%%%
%%% TODO: iota depends on the round index, so this code may need to be altered
%%% slightly.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-spec inner_keccak(Sponge) -> NewSponge
when Sponge :: <<_:1600>>,
NewSponge :: <<_:1600>>.
%% @private
%% the "inner keccak" function, or the 'f' function
%% a bunch of bit bullshit
%% @end
inner_keccak(Sponge) ->
rounds(Sponge, 24).
-spec rounds(Sponge, NumRoundsLeft) -> ResultSponge
when Sponge :: <<_:1600>>,
NumRoundsLeft :: non_neg_integer(),
ResultSponge :: <<_:1600>>.
%% @private
%% do however many rounds
%% @end
rounds(Sponge, NumRoundsLeft) when 1 =< NumRoundsLeft ->
NewSponge = rnd(Sponge),
NewNumRoundsLeft = NumRoundsLeft - 1,
rounds(NewSponge, NewNumRoundsLeft);
% no rounds left
rounds(FinalSponge, 0) ->
FinalSponge.
-spec rnd(Sponge) -> NewSponge
when Sponge :: <<_:1600>>,
NewSponge :: <<_:1600>>.
%% @private
%% do a single round
%% @private
rnd(Sponge) ->
iota(chi(pi(rho(theta(Sponge))))).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% THETA STEP
%%
%% For each bit in the input array,
%% 1. take
%% - the bit
%% - the 5-bit column to the left
%% - the 5-bit column to the front right
%% 2. compute the parity of their concatenation (0 if even# of 1s, 1 if odd# of
%% 1s)
%% 3. set the bit to that parity value
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-spec theta(Array) -> NewArray
when Array :: <<_:1600>>,
NewArray :: <<_:1600>>.
%% @private
%% the theta step
%% go bit by bit, applying a weird transformation to each bit
%% @end
theta(Array) ->
theta(Array, 0).
-spec theta(Array, Idx0) -> NewArray
when Array :: <<_:1600>>,
Idx0 :: 0..1599,
NewArray :: <<_:1600>>.
%% @private
%% the theta step
%% go bit by bit, applying a weird transformation to each bit
%% @end
% done
theta(ResultArray, 1600) ->
ResultArray;
% do the weird permutation
% x = left/right -/+
% y = down/up -/+
% z = outOfScreen/intoScreen -/+
% front/behind -/+
% left-handed coordinate system but what can you do
theta(ArrayBits, ThisIdx0) ->
<<Before:ThisIdx0, ThisBit:1, Rest/bitstring>> = ArrayBits,
{xyz, ThisX, _ThisY, ThisZ} = idx0_to_xyz(ThisIdx0),
XToTheLeft = left(ThisX),
XToTheRight = right(ThisX),
ZToTheFront = front(ThisZ),
ColumnToTheLeft = xzth({xz, XToTheLeft, ThisZ}, ArrayBits),
ColumnToTheFrontRight = xzth({xz, XToTheRight, ZToTheFront}, ArrayBits),
NewBit = parity(<<ColumnToTheLeft/bitstring, ColumnToTheFrontRight/bitstring, ThisBit:1>>),
NewBits = <<Before:ThisIdx0, NewBit:1, Rest/bitstring>>,
NewIdx0 = ThisIdx0 + 1,
theta(NewBits, NewIdx0).
-spec parity(Bits) -> Parity
when Bits :: bitstring(),
Parity :: 0 | 1.
%% @private
%% Count the number of 1s in the given bitstring. Return 0 if even, 1 if odd.
%% @end
parity(Bits) ->
parity(Bits, 0).
parity(<<0:1, Rest/bitstring>>, NOnes) -> parity(Rest, NOnes);
parity(<<1:1, Rest/bitstring>>, NOnes) -> parity(Rest, NOnes + 1);
parity(<<>> , NOnes) -> NOnes rem 2.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% RHO STEP
%%
%% This step applies an affine shift to each 64-bit "lane" (fixed X,Y; Z ranges
%% from 0 to 63).
%%
%% The amount of the shift is given by the "offset" table.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-spec rho(Array) -> NewArray
when Array :: <<_:1600>>,
NewArray :: <<_:1600>>.
%% @private
%% do the rho step
%% @end
rho(Array) ->
rho(Array, {xy, 0, 0}).
-spec rho(Array, LaneXY) -> NewArray
when Array :: <<_:1600>>,
LaneXY :: {xy, 0..4, 0..4},
NewArray :: <<_:1600>>.
%% @private
%% do the rho step to each lane
%% @end
% terminal case
rho(Array, XY = {xy, 4, 4}) ->
Result = rhoxy(Array, XY),
Result;
% need to reset Y and increment X
rho(Array, XY = {xy, X, 4}) ->
NewArray = rhoxy(Array, XY),
NewXY = {xy, X + 1, 0},
rho(NewArray, NewXY);
% need to increment Y and leave X
rho(Array, XY = {xy, X, Y}) ->
NewArray = rhoxy(Array, XY),
NewXY = {xy, X, Y + 1},
rho(NewArray, NewXY).
-spec rhoxy(Array1600, LaneXY) -> NewArray1600
when Array1600 :: <<_:1600>>,
LaneXY :: {xy, 0..4, 0..4},
NewArray1600 :: <<_:1600>>.
%% @private
%% do the rho step to a given lane
%% @end
rhoxy(Array, ThisXY = {xy, ThisX, ThisY}) ->
ThisOffset = offset(ThisX, ThisY),
ThisLane = xyth(ThisXY, Array),
% we increase the z coordinate by the offset
% Suppose the offset is 2
% bits = A B C D E
% z = 0 1 2 3 4
% newbits = D E A B C
% in other words, we take Offset number of bits off the tail of the lane
% put them on the front
<<Foo:(64 - ThisOffset), Bar:ThisOffset>> = ThisLane,
NewLane = <<Bar:ThisOffset, Foo:(64 - ThisOffset)>>,
NewArray = xyset(ThisXY, Array, NewLane),
NewArray.
-spec offset(X, Y) -> Offset
when X :: 0..4,
Y :: 0..4,
Offset :: 0..63.
%% @private
%% See NIST specification, pg. 21
%% @end
offset(3, 2) -> 153 rem 64;
offset(3, 1) -> 55 rem 64;
offset(3, 0) -> 28 rem 64;
offset(3, 4) -> 120 rem 64;
offset(3, 3) -> 21 rem 64;
offset(4, 2) -> 231 rem 64;
offset(4, 1) -> 276 rem 64;
offset(4, 0) -> 91 rem 64;
offset(4, 4) -> 78 rem 64;
offset(4, 3) -> 136 rem 64;
offset(0, 2) -> 3 rem 64;
offset(0, 1) -> 36 rem 64;
offset(0, 0) -> 0 rem 64;
offset(0, 4) -> 210 rem 64;
offset(0, 3) -> 105 rem 64;
offset(1, 2) -> 10 rem 64;
offset(1, 1) -> 300 rem 64;
offset(1, 0) -> 1 rem 64;
offset(1, 4) -> 66 rem 64;
offset(1, 3) -> 45 rem 64;
offset(2, 2) -> 171 rem 64;
offset(2, 1) -> 6 rem 64;
offset(2, 0) -> 190 rem 64;
offset(2, 4) -> 253 rem 64;
offset(2, 3) -> 15 rem 64.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% PI STEP
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-spec pi(Array1600) -> NewArray1600
when Array1600 :: <<_:1600>>,
NewArray1600 :: <<_:1600>>.
%% @private
%% The effect of this step is to rearrange the lanes
%%
%% Result[X, Y] = Input[X + 3*Y, X]
%%
%% (mod 5 of course)
%% @end
pi(Array1600) ->
% what I'm going to make is a map #{{xy, X, Y} := Lane}
% then make a new lane map from the original
% then convert it back into
OriginalLaneMap = lane_map(Array1600, #{}, {xy, 0, 0}),
NewLaneMap = new_lane_map(OriginalLaneMap, #{}, {xy, 0, 0}),
NewArray1600 = lane_map_to_arr1600(NewLaneMap, <<0:1600>>, {xy, 0, 0}),
NewArray1600.
-spec lane_map(Array1600, MapAcc, Coord) -> LaneMap
when Array1600 :: <<_:1600>>,
MapAcc :: #{XY := Lane},
Coord :: XY,
LaneMap :: #{XY := Lane},
XY :: {xy, X :: 0..4, Y :: 0..4},
Lane :: <<_:64>>.
%% @private
%% Make a map #{XY := Lane}
%% @end
% terminal case, end of array
lane_map(Array1600, MapAcc, ThisXY = {xy, 4, 4}) ->
ThisLane = xyth(ThisXY, Array1600),
FinalMap = MapAcc#{ThisXY => ThisLane},
FinalMap;
% end of Y value, set Y to 0 and increment X
lane_map(Array1600, MapAcc, ThisXY = {xy, X, 4}) ->
ThisLane = xyth(ThisXY, Array1600),
NewMapAcc = MapAcc#{ThisXY => ThisLane},
NewXY = {xy, X + 1, 0},
lane_map(Array1600, NewMapAcc, NewXY);
% general case: increment Y value
lane_map(Array1600, MapAcc, ThisXY = {xy, X, Y}) ->
ThisLane = xyth(ThisXY, Array1600),
NewMapAcc = MapAcc#{ThisXY => ThisLane},
NewXY = {xy, X, Y + 1},
lane_map(Array1600, NewMapAcc, NewXY).
-spec new_lane_map(LaneMap, MapAcc, Coord) -> NewLaneMap
when LaneMap :: #{XY := Lane},
MapAcc :: LaneMap,
Coord :: XY,
NewLaneMap :: LaneMap,
XY :: {xy, X :: 0..4, Y :: 0..4},
Lane :: <<_:64>>.
%% @private
%% The effect of this step is to rearrange the lanes
%%
%% Result[X, Y] = Input[X + 3*Y, X]
%%
%% (mod 5 of course)
%% @end
% terminal case, end of array
new_lane_map(OrigLaneMap, MapAcc, ThisXY = {xy, 4, 4}) ->
OrigXY = xytrans(ThisXY),
ThisLane = maps:get(OrigXY, OrigLaneMap),
FinalMap = MapAcc#{ThisXY => ThisLane},
FinalMap;
% end of Y value, set Y to 0 and increment X
new_lane_map(OrigLaneMap, MapAcc, ThisXY = {xy, X, 4}) ->
OrigXY = xytrans(ThisXY),
ThisLane = maps:get(OrigXY, OrigLaneMap),
NewMapAcc = MapAcc#{ThisXY => ThisLane},
NewXY = {xy, X + 1, 0},
new_lane_map(OrigLaneMap, NewMapAcc, NewXY);
% general case: increment Y value
new_lane_map(OrigLaneMap, MapAcc, ThisXY = {xy, X, Y}) ->
OrigXY = xytrans(ThisXY),
ThisLane = maps:get(OrigXY, OrigLaneMap),
NewMapAcc = MapAcc#{ThisXY => ThisLane},
NewXY = {xy, X, Y + 1},
new_lane_map(OrigLaneMap, NewMapAcc, NewXY).
-spec xytrans(ResultXY) -> InputXY
when ResultXY :: XY,
InputXY :: XY,
XY :: {xy, X :: 0..4, Y :: 0..4}.
%% @private
%% Result[X, Y] = Input[X + 3*Y, X]
%%
%% See NIST doc, pp. 14
xytrans({xy, X, Y}) ->
{xy, (X + 3*Y) rem 5, X}.
-spec lane_map_to_arr1600(LaneMap, Array1600Acc, Coord) -> Array1600
when LaneMap :: #{XY := Lane},
Array1600Acc :: Array1600,
Coord :: XY,
Array1600 :: <<_:1600>>,
XY :: {xy, X :: 0..4, Y :: 0..4},
Lane :: <<_:64>>.
%% @private
%% inverse of lane_map/3
%%
%% it would probably faster to concatenate an accumulator, but that requires
%% iterating in the correct order, and i'm more comfortable calling xyset/3
%% @end
% terminal case, end of array
lane_map_to_arr1600(LaneMap, Array1600Acc, ThisXY = {xy, 4, 4}) ->
ThisLane = maps:get(ThisXY, LaneMap),
FinalArray1600Acc = xyset(ThisXY, Array1600Acc, ThisLane),
FinalArray1600Acc;
% end of Y value, set Y to 0 and increment X
lane_map_to_arr1600(LaneMap, Array1600Acc, ThisXY = {xy, X, 4}) ->
ThisLane = maps:get(ThisXY, LaneMap),
NewArray1600Acc = xyset(ThisXY, Array1600Acc, ThisLane),
NewXY = {xy, X + 1, 0},
lane_map_to_arr1600(LaneMap, NewArray1600Acc, NewXY);
% general case: increment Y value
lane_map_to_arr1600(LaneMap, Array1600Acc, ThisXY = {xy, X, Y}) ->
ThisLane = maps:get(ThisXY, LaneMap),
NewArray1600Acc = xyset(ThisXY, Array1600Acc, ThisLane),
NewXY = {xy, X, Y + 1},
lane_map_to_arr1600(LaneMap, NewArray1600Acc, NewXY).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% CHI STEP
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
chi(_Sponge) ->
error(nyi).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% IOTA STEP
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
iota(_Sponge) ->
error(nyi).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% INNER KECCAK COORDINATE SYSTEM
%%%
%%% Inner Keccak thinks of the 1600-bit input array as a 5x5x64 3D array
%%% (5*5*64). This section provides a variety of helper functions to talk about
%%% the array using the X,Y,Z coordinate system.
%%%
%%% The coordinate system is toroidal, meaning that each coordinate is "modded
%%% down" to be in the approprate range. For instance, the X-coordinate "to the
%%% right" of X=4 is X=0. And likewise, the coordinate "behind" Z=63 is Z=0. See
%%% the section on directionality conventions.
%%%
%%% VOCABULARY:
%%%
%%% 3D state:
%%% - The [state] is the entire 5x5x24 array
%%%
%%% 0D subsets of the state:
%%% - A [bit] is a single bit in the array given by an X,Y,Z coordinate triple
%%% (see xyzth/3).
%%%
%%% 1D subsets of the state:
%%% - a [row]
%%% - is a 5-bit array
%%% - given by a Y,Z coordinate pair in range {0..4, 0..63} (see yzth/2)
%%% - you should think of a row as being internally indexed with an X
%%% coordinate ranging in 0..4
%%% - a [column]
%%% - is a 5-bit array
%%% - given by an X,Z coordinate pair in range {0..4, 0..63} (see xzth/2)
%%% - you should think of a column as being internally indexed with a Y
%%% coordinate ranging in 0..4
%%% - a [lane]
%%% - is a 64-bit array
%%% - given by an X,Y coordinate pair in range {0..4, 0..4} (see xyth/2)
%%% - you should think of a lane as being internally indexed with a Z
%%% coordinate ranging in 0..63.
%%%
%%% 2D subsets of the state:
%%% - a [sheet]
%%% - is a 5x64 array
%%% - given by a single X coordinate ranging in 0..4 (see xth/2)
%%% - you should think of a sheet as being internally indexed by a Y,Z
%%% coordinate pair ranging in {0..4, 0..63}.
%%% - a [plane]
%%% - is a 5x64 array
%%% - given by a single Y coordinate ranging in 0..4 (see yth/2)
%%% - you should think of a plane as being internally indexed by a X,Z
%%% coordinate pair ranging in {0..4, 0..63}.
%%% - a [slice]
%%% - is a 5x5 array
%%% - given by a single Z coordinate ranging in 0..63 (see zth/2)
%%% - you should think of a sheet as being internally indexed by a X,Y
%%% coordinate pair ranging in {0..4, 0..4}.
%%%
%%% PACKING CONVENTION:
%%%
%%% Each lane (64-bit long bitstring given by an {X,Y} <- {0..4, 0..4} coordinate
%%% pair and indexed by a Z <- 0..63 coordinate.
%%%
%%% DIRECTIONALITY CONVENTION:
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% CONVERTING BETWEEN XYZ-INDICES AND 0-INDICES
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-spec idx0_to_xyz(Idx0) -> XYZ
when Idx0 :: 0..1599,
XYZ :: {xyz, X :: 0..4, Y :: 0..4, Z :: 0..63}.
%% @private
%% Convert a 0-index to an XYZ-index
%% @end
idx0_to_xyz(Idx0) ->
% it's sort of retarded endian notation
% drunk endian notation
% YXZ
% yes, that order
% Z is in the range 0..63
% X is in the range 0..4
% Y is in the range 0..4
{Q1, Z} = {Idx0 div 64, Idx0 rem 64},
{Q2, X} = { Q1 div 5, Q1 rem 5},
{ 0, Y} = { Q2 div 5, Q2 rem 5},
{xyz, X, Y, Z}.
-spec xyz_to_idx0(XYZ) -> Idx0
when XYZ :: {xyz, X :: 0..4, Y :: 0..4, Z :: 0..63},
Idx0 :: 0..1599.
%% @private
%% Convert an XYZ-index into a 0-index
%% @end
xyz_to_idx0({xyz, X, Y, Z}) ->
% reverse of the above
% drunk endian notation
% YXZ
% to get the "X place", multiply X by 64
% to get the "Y place", multiply Y by 64*5
Y*64*5 + X*64 + Z.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% DIRECTIONAL TRANSFORMATIONS ON SINGLE COORDINATE VALUES
%%
%% For instance, if you have an X-value and want to get the X-value "to the
%% left", this section contains functions that compute such things.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-spec left(X) -> XToTheLeft
when X :: 0..4,
XToTheLeft :: 0..4.
%% @private
%% x = left/right
%% -/+
%% @end
left(0) -> 4;
left(1) -> 0;
left(2) -> 1;
left(3) -> 2;
left(4) -> 3.
-spec right(X) -> XToTheRight
when X :: 0..4,
XToTheRight :: 0..4.
%% @private
%% x = left/right
%% -/+
%% @end
right(0) -> 1;
right(1) -> 2;
right(2) -> 3;
right(3) -> 4;
right(4) -> 0.
-spec down(Y) -> YBelow
when Y :: 0..4,
YBelow :: 0..4.
%% @private
%% y = down/up
%% -/+
%% @end
down(0) -> 4;
down(1) -> 0;
down(2) -> 1;
down(3) -> 2;
down(4) -> 3.
-spec up(Y) -> YAbove
when Y :: 0..4,
YAbove :: 0..4.
%% @private
%% y = down/up
%% -/+
%% @end
up(0) -> 1;
up(1) -> 2;
up(2) -> 3;
up(3) -> 4;
up(4) -> 0.
-spec front(Z) -> ZInFront
when Z :: 0..63,
ZInFront :: 0..63.
%% @private
%% z = front/behind
%% -/+
%% @end
front(0) -> 63;
front(N) when 1 =< N, N =< 63 -> N - 1.
-spec behind(Z) -> ZBehind
when Z :: 0..63,
ZBehind :: 0..63.
%% @private
%% z = front/behind
%% -/+
%% @end
behind(N) when 0 =< N, N =< 62 -> N + 1;
behind(63) -> 0.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% 0D BIT ACCESSORS
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-spec xyzth(XYZ, Array1600) -> Bit
when XYZ :: {xyz, X, Y, Z},
Array1600 :: <<_:1600>>,
Bit :: 0 | 1,
X :: 0..4,
Y :: 0..4,
Z :: 0..63.
%% @private
%% Fetch the bit at the given X, Y, Z coordinate triple
%% @end
xyzth(XYZ, Array1600) ->
Idx0 = xyz_to_idx0(XYZ),
<<_Skip:Idx0, Bit:1, _Rest/bitstring>> = Array1600,
Bit.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% 1D SUBSET ACCESSORS
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-spec xzth(XZ, Bits) -> Column
when XZ :: {xz, X, Z},
X :: 0..4,
Z :: 0..63,
Bits :: <<_:1600>>,
Column :: <<_:5>>.
%% @private
%% Fetch the column at the given X, Z coordinate pair
%% @end
xzth({xz, X, Z}, Bits) ->
% just grab them one at a time
<< <<( xyzth({xyz, X, Y, Z}, Bits) ):1>>
|| Y <- lists:seq(0, 4)
>>.
-spec xyth(XY, Array1600) -> Lane
when XY :: {xy, X, Y},
Array1600 :: <<_:1600>>,
Lane :: <<_:64>>,
X :: 0..4,
Y :: 0..4.
%% @private
%% Grab the lane at the given X, Y coordinate pair.
%% @end
xyth({xy, X, Y}, Array1600) ->
<< <<( xyzth({xyz, X, Y, Z}, Array1600) ):1>>
|| Z <- lists:seq(0, 63)
>>.
-spec xyset(LaneXY, Array1600, NewLane) -> NewArray1600
when Array1600 :: <<_:1600>>,
LaneXY :: {xy, 0..4, 0..4},
NewLane :: <<_:64>>,
NewArray1600 :: <<_:1600>>.
%% @private
%% Take the original array, and swap out the lane at the given x,y coordinate
%% with the new given lane.
%%
%% The lane will be represented continuously so we can do a hack
%% @end
% special case when it's the last lane
% grab the final 64 bits off the original array and replace them with the new lane
xyset(_LaneXY = {xy, 4, 4}, <<Pre:(1600 - 64), _:64>>, NewLane) ->
<<Pre:(1600 - 64), NewLane/bitstring>>;
% general case, grab the shit before the lane, grab the shit after the lane
% replace the shit in the middle
xyset(_LaneXY = {xy, LaneX, LaneY}, OriginalArray, NewLane) ->
FirstBitOfLane_Idx0 = xyz_to_idx0({xyz, LaneX, LaneY, 0}),
FirstBitAfterLane_Idx0 = xyz_to_idx0({xyz, LaneX, LaneY, 63}) + 1,
NumberOfBitsBeforeTheLane = FirstBitOfLane_Idx0,
NumberOfBitsIncludingTheLane = FirstBitAfterLane_Idx0,
<<PreLane:NumberOfBitsBeforeTheLane , _/bitstring>> = OriginalArray,
<< _:NumberOfBitsIncludingTheLane, AfterLane/bitstring>> = OriginalArray,
Result = <<PreLane:NumberOfBitsBeforeTheLane, NewLane/bitstring, AfterLane/bitstring>>,
Result.