commit a0df7044512f91cf9dac936a15f960ae40bccf9f Author: Peter Harpending Date: Sun Oct 16 18:48:57 2022 -0600 KEEEK diff --git a/kek.erl b/kek.erl new file mode 100644 index 0000000..5aeb2fa --- /dev/null +++ b/kek.erl @@ -0,0 +1,579 @@ +%% @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") +%% @end +-module(kek). + +% theta and rho steps are done +% weird syntax errors + +-compile(export_all). + +sha3_224(Message) -> + sha3(224, Message). + +sha3_256(Message) -> + sha3(256, Message). + +sha3_384(Message) -> + sha3(384, Message). + +sha3_512(Message) -> + sha3(512, Message). + +sha3(OutputBitLength, Message) -> + Capacity = 2*OutputBitLength, + ShaMessage = <>, + keccak(Capacity, ShaMessage, OutputBitLength). + + +shake128(Message, OutputBitLength) -> + shake(128, Message, OutputBitLength). + +shake256(Message, OutputBitLength) -> + shake(256, Message, OutputBitLength). + +shake(ShakeNumber, Message, OutputBitLength) -> + Capacity = 2*ShakeNumber, + ShakeMessage = <>, + keccak(Capacity, ShakeMessage, OutputBitLength). + + + +-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 = <>, + 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 = <>, + 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 = <>, + 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) -> + <> = PaddedMessageBits, + % we bitwise xor the sponge against the r word followed by a bunch of 0s + <> = Sponge, + <> = <>, + FInputInt = SpongeInt bxor Foo, + FInputBits = <>, + 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(<>, OutputBitLength, _BitRate) -> +%%% <>; +% 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) -> + <> = FinalAccBits, + <>; +% 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)-> + <> = WetSponge, + NewResultAcc = <>, + NewWetSponge = inner_keccak(WetSponge), + really_squeeze(NewWetSponge, OutputBitLength, BitRate, NewResultAcc). + + + +-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 :: pos_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))))). + + + +-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) -> + <> = 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(<>), + NewBits = <>, + NewIdx0 = ThisIdx0 + 1, + theta(NewBits, NewIdx0). + + + +-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(Array, LaneXY) -> NewArray + when Array :: <<_:1600>>, + LaneXY :: {xy, 0..4, 0..4}, + NewArray :: <<_: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 + <> = ThisLane, + NewLane = <>, + NewArray = replace_lane(Array, ThisXY, NewLane), + NewArray. + +xyth(_, _) -> error(nyi). + +-spec replace_lane(OriginalArray, LaneXY, NewLane) -> NewArray + when OriginalArray :: <<_:1600>>, + LaneXY :: {xy, 0..4, 0..4}, + NewLane :: <<_:64>>, + NewArray :: <<_: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 +replace_lane(<>, _LaneXY = {xy, 4, 4}, NewLane) -> + <>; +% general case, grab the shit before the lane, grab the shit after the lane +% replace the shit in the middle +replace_lane(OriginalArray, _LaneXY = {xy, LaneX, LaneY}, 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, + <> = OriginalArray, + << _:NumberOfBitsIncludingTheLane, AfterLane/bitstring>> = OriginalArray, + Result = <>, + Result. + + + +-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(_Sponge) -> + error(nyi). + +chi(_Sponge) -> + error(nyi). + +iota(_Sponge) -> + error(nyi). + + + +-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. + + + +-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. + + + +-spec xzth(XZ, Bits) -> Column + when XZ :: {xz, X, Z}, + X :: 0..4, + Z :: 0..63, + Bits :: <<_:1600>>, + Column :: <<_:5>>. + +xzth({xz, X, Z}, Bits) -> + % just grab them one at a time + << <<( xyzth({xyz, X, Y, Z}, Bits) ):1>> + || Y <- lists:seq(0, 4) + >>. + +xyzth(_, _) -> error(nyi). + + +-spec parity(bitstring()) -> 0 | 1. + +%% @private +%% count the number of 1s in the bitstring (is it even or 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.