diff -uNr a/eucrypt/smg_keccak/smg_keccak.adb b/eucrypt/smg_keccak/smg_keccak.adb --- a/eucrypt/smg_keccak/smg_keccak.adb 49e31942e59726fde97368b5651502c78efa7602b31e77bdce9ae745fab817cbcacf9c166022805bb20ba25ad693cfcf5cf9841252044f5bea30c89792e1a1e5 +++ b/eucrypt/smg_keccak/smg_keccak.adb 87241e219925cce47cd73892dc728607f14bd01324ba0b08d04db6f2d7f2469958d4c65a5f3c26f2db1857841857dcd3204fdc78238cae3126905bb5a3cecc99 @@ -2,6 +2,166 @@ package body SMG_Keccak is +-- public function, sponge + procedure Sponge( Input : in Bitstream; + Block_Len : in Keccak_Rate; + Output : out Bitstream) is + Internal : State := (others => (others => 0)); + begin + --absorb input into sponge in a loop on available blocks, including padding + declare + -- number of input blocks after padding (between 2 and block_len bits pad) + Padded_Blocks : constant Positive := 1 + (Input'Length + 1) / Block_Len; + Padded : Bitstream ( 1 .. Padded_Blocks * Block_Len ); + Block : Bitstream ( 1 .. Block_Len ); + begin + -- initialise Padded with 0 everywhere + Padded := ( others => 0 ); + -- copy and pad input with rule 10*1 + Padded( Padded'First .. Padded'First + Input'Length - 1 ) := Input; + Padded( Padded'First + Input'Length ) := 1; + Padded( Padded'Last ) := 1; + + -- loop through padded input and absorb block by block into sponge + -- padded input IS a multiple of blocks, so no stray bits left + for B in 0 .. Padded_Blocks - 1 loop + -- first get the current block to absorb + Block := Padded( Padded'First + B * Block_Len .. + Padded'First + (B+1) * Block_Len - 1 ); + AbsorbBlock( Block, Internal ); + -- scramble state with Keccak function + Internal := Keccak_Function( Internal ); + + end loop; -- end absorb loop for blocks + end; -- end absorb stage + + --squeeze required bits from sponge in a loop as needed + declare + -- full blocks per output + BPO : constant Natural := Output'Length / Block_Len; + -- stray bits per output + SPO : constant Natural := Output'Length mod Block_Len; + Block : Bitstream( 1 .. Block_Len ); + begin + -- squeeze block by block (if at least one full block is needed) + for I in 0 .. BPO - 1 loop + SqueezeBlock( Block, Internal ); + Output( Output'First + I * Block_Len .. + Output'First + (I + 1) * Block_Len -1) := Block; + + -- scramble state + Internal := Keccak_Function( Internal ); + end loop; -- end squeezing full blocks + + -- squeeze any partial block needed (stray bits) + if SPO > 0 then + SqueezeBlock( Block, Internal ); + Output( Output'Last - SPO + 1 .. Output'Last ) := + Block( Block'First .. Block'First + SPO - 1 ); + end if; -- end squeezing partial last block (stray bits) + + end; -- end squeeze stage + end Sponge; + + -- convert from a bitstream of ZWord size to an actual ZWord number + -- first bit of bitstream will be most significant bit of ZWord + function BitsToWord( Bits: in Bitword ) return ZWord is + W: ZWord; + P: Natural; + begin + W := 0; + P := 0; + for I in reverse Bitword'Range loop + W := W + ZWord( Bits(I) ) * ( 2**P ); + P := P + 1; + end loop; + return W; + end BitsToWord; + + -- convert from a ZWord (lane of state) to a bitstream of ZWord size + -- most significant bit of ZWord will be left most bit of bitstream + function WordToBits( Word: in ZWord ) return Bitword is + Bits: Bitword := (others => 0); + W: ZWord; + begin + W := Word; + for I in reverse Bitword'Range loop + Bits( I ) := Bit( W mod 2 ); + W := W / 2; + end loop; + return Bits; + end WordToBits; + +-- helper procedures for sponge absorb/squeeze + + -- NO scramble here, this will absorb ALL given block, make sure it fits! + procedure AbsorbBlock( Block: in Bitstream; S: in out State ) is + WPB: constant Natural := Block'Length / Z_Length; -- words per block + SBB: constant Natural := Block'Length mod Z_Length; -- stray bits + FromPos, ToPos : Natural; + X, Y : XYCoord; + Word : ZWord; + BWord : Bitword; + begin + -- xor current block into first Block'Length bits of state + -- a block can consist in more than one word + X := 0; + Y := 0; + for I in 0..WPB-1 loop + FromPos := Block'First + I * Z_Length; + ToPos := FromPos + Z_Length - 1; + Word := BitsToWord( Block( FromPos .. ToPos ) ); + S( X, Y ) := S( X, Y ) xor Word; + -- move on to next word in state + X := X + 1; + if X = 0 then + Y := Y + 1; + end if; + end loop; + -- absorb also any remaining bits from block + if SBB > 0 then + ToPos := Block'Last; + FromPos := ToPos - SBB + 1; + BWord := (others => 0); + BWord(Bitword'First .. Bitword'First + SBB - 1) := Block(ToPos..FromPos); + Word := BitsToWord( BWord ); + S( X, Y ) := S( X, Y ) xor Word; + end if; + end AbsorbBlock; + + -- NO scramble here, this will squeeze Block'Length bits out of *same* state S + procedure SqueezeBlock( Block: out Bitstream; S: in State) is + X, Y : XYCoord; + BWord : Bitword; + FromPos : Natural; + Len : Natural; + begin + X := 0; + Y := 0; + FromPos := Block'First; + + while FromPos <= Block'Last loop + BWord := WordToBits( S(X, Y) ); + + X := X + 1; + if X = 0 then + Y := Y + 1; + end if; + + -- copy full word if it fits or + -- only as many bits as are still needed to fill the block + Len := Block'Last - FromPos + 1; + if Len > Z_Length then + Len := Z_Length; + end if; + + Block(FromPos..FromPos+Len-1) := BWord(BWord'First..BWord'First+Len-1); + FromPos := FromPos + Len; + end loop; + end SqueezeBlock; + + +-- private, internal transformations function Theta(Input : in State) return State is Output : State; C : Plane; diff -uNr a/eucrypt/smg_keccak/smg_keccak.ads b/eucrypt/smg_keccak/smg_keccak.ads --- a/eucrypt/smg_keccak/smg_keccak.ads 1e1eaa78eccaffa4235ec739a63d20ee3f399bdd94386915b44ce58212032988ce67481eb2e558c846dfcdcdd8748bda6e276d20512b487f5a3b86c44c946a40 +++ b/eucrypt/smg_keccak/smg_keccak.ads 9356acb04f2091a9a611331387e055bcb8e58e7b28ae7fe4e0562486c802598ad3f24bbc20f05e539a3db6274e75cd02f5a31dac5bb2499f3cf2bf094994ddc0 @@ -30,10 +30,45 @@ type Round_Constants is array(Round_Index) of ZWord; --magic keccak constants + -- rate can be chosen by caller at each call, between 1 and width of state + -- higher rate means sponge "eats" more bits at a time but has fewer bits in + -- the "secret" part of the state (i.e. lower capacity) + subtype Keccak_Rate is Positive range 1..Width; -- capacity = width - rate + + type Bit is mod 2; + type Bitstream is array( Natural range <> ) of Bit; -- any length; message + subtype Bitword is Bitstream( 0..Z_Length - 1 ); -- bits of one state "word" + + -- type conversions + function BitsToWord( Bits: in Bitword ) return ZWord; + function WordToBits( Word: in ZWord ) return Bitword; + + -- public function, the sponge itself + -- Keccak sponge structure using Keccak_Function, Pad and a given bitrate; + -- Input - the stream of bits to hash (the message) + -- Block_Len - the bitrate to use; this is effectively the block length + -- for splitting Input AND squeezing output between scrambles + -- Output - a bitstream of desired size for holding output + procedure Sponge(Input : in Bitstream; + Block_Len : in Keccak_Rate; + Output : out Bitstream); + private -- these are internals of the keccak implementation, not meant to be directly -- accessed/used + -- this will squeeze Block'Length bits out of state S + -- NO scramble of state in here! + -- NB: make SURE that Block'Length is the correct bitrate for this sponge + -- in particular, Block'Length should be a correct bitrate aka LESS than Width + procedure SqueezeBlock( Block: out Bitstream; S: in State); + + -- This absorbs into sponge the given block, modifying the state accordingly + -- NO scramble of state in here so make sure the whole Block fits in state! + -- NB: make SURE that Block'Length is *the correct bitrate* for this sponge + -- in particular, Block'Length should be a correct bitrate aka LESS than Width + procedure AbsorbBlock( Block: in Bitstream; S: in out State ); + --Keccak magic numbers RC : constant Round_Constants := ( @@ -74,15 +109,15 @@ return ZWord; pragma Import(Intrinsic, Shift_Right); - --Keccak permutations + --Keccak transformations of the internal state function Theta ( Input : in State) return State; function Rho ( Input : in State) return State; function Pi ( Input : in State) return State; function Chi ( Input : in State) return State; function Iota ( Round_Const : in ZWord; Input : in State) return State; - --Keccak full function with block width currently 1600 (Width constant above) - --this simply applies *all* keccak permutations in the correct order and using + --Keccak function with block width currently 1600 (Width constant above) + --this simply applies *all* keccak transformations in the correct order, using -- the keccak magic numbers (round constants) as per keccak reference function Keccak_Function(Input: in State) return State; diff -uNr a/eucrypt/smg_keccak/tests/smg_keccak-test.adb b/eucrypt/smg_keccak/tests/smg_keccak-test.adb --- a/eucrypt/smg_keccak/tests/smg_keccak-test.adb e0071e8372c50670886565cc5ef6f69e310ab5f4922abd6cb20fa281087c6790ce0ec1980af4a2504dcb189a8009cf5354d7581cf9b4f4d1f515086ecd08b248 +++ b/eucrypt/smg_keccak/tests/smg_keccak-test.adb b91e0448a3d5ec419c7e47701e8a3bfc53082601b860ac093edd13606c093d1c6f112f514a8a90c0825713b45b5c273145d67d90d291631d6aaf940300df9386 @@ -11,7 +11,6 @@ type Test_Round is array(Round_Index) of Test_Vector; --helper methods - procedure print_state(S: in State; Title: in String) is Hex: array(0..15) of Character := ("0123456789ABCDEF"); Len: constant Natural := Z_Length / 4; @@ -32,6 +31,24 @@ end loop; end; + procedure print_bitstream(B: in Bitstream; Title: in String) is + Hex : array(0..15) of Character := ("0123456789ABCDEF"); + HexString : String(1..B'Length/4); + C : Natural; + Pos : Natural; + begin + for I in 1..B'Length/4 loop + Pos := (I-1)*4 + B'First; + C := Natural( B(Pos) ) * 8 + + Natural( B(Pos + 1) ) * 4 + + Natural( B(Pos + 2) ) * 2 + + Natural( B(Pos + 3) ); + HexString(I) := Hex(C); + end loop; + Put_Line("---" & Title & "---"); + Put_Line(HexString); + end print_bitstream; + function read_state(File: in FILE_TYPE; Oct: Positive :=8) return State is S: State; Line1: String := "0000000000000000 " & @@ -140,6 +157,117 @@ Input := Expected; end loop; end test_one_round; + + procedure test_bits_to_word_conversion is + bits: Bitword := (others => 0); + obtained_bits: Bitword := (others => 0); + expected: ZWord; + obtained: ZWord; + begin + expected := 16#E7DDE140798F25F1#; + bits := (1,1,1,0, 0,1,1,1, 1,1,0,1, 1,1,0,1, 1,1,1,0, 0,0,0,1, 0,1,0,0, + 0,0,0,0, 0,1,1,1, 1,0,0,1, 1,0,0,0, 1,1,1,1, 0,0,1,0, 0,1,0,1, + 1,1,1,1, 0,0,0,1); + obtained := BitsToWord(bits); + obtained_bits := WordToBits(expected); + + if obtained /= expected then + Put_Line("FAIL: bits to word"); + Put_Line("Expected: " & ZWord'Image(expected)); + Put_Line("Obtained: " & ZWord'Image(obtained)); + else + Put_Line("PASSED: bits to word"); + end if; + + if obtained_bits /= bits then + Put_Line("FAIL: word to bits"); + Put("Expected: "); + for I in Bitword'Range loop + Put(Bit'Image(bits(I))); + end loop; + Put_Line(""); + Put_Line("Obtained: "); + for I in Bitword'Range loop + Put(Bit'Image(obtained_bits(I))); + end loop; + Put_Line(""); + else + Put_Line("PASSED: word to bits"); + end if; + end test_bits_to_word_conversion; + + procedure test_sponge is + Bitrate : constant Keccak_Rate := 1344; + Input : Bitstream(1..5) := (1, 1, 0, 0, 1); + Output : Bitstream(1..Bitrate*2); + Hex : array(0..15) of Character := ("0123456789ABCDEF"); + HexString : String(1..Bitrate/2); + C : Natural; + ExpHex : String(1..Bitrate/2); + Error : Natural; + Pos : Natural; + begin + ExpHex := "B57B7DAED6330F79BA5783C5D45EABFFA1461FAC6CEA09BD"& + "AAC114F17E23E5B349EECBC907E07FA36ECF8374079811E8"& + "5E49243D04182C389E68C733BE698468423DB9891D3A7B10"& + "320E0356AB4AB916F68C0EA20365A1D4DBA48218CA89CBB8"& + "6D08A34E04544D4100FFE9CB138EADC2D3FC0E8CC2BC15A7"& + "5B950776970BFC310F33BF609630D73CAD918CF54657589E"& + "42CF7CBF20DE677D2AB7E49389F6F6C3B3FE2992905325CE"& + "60931C1515043595ADC1619CB7E034EF52BDC485D03B7FDD"& + "7345E849FFB4C4426195C8D88C1E7BF9ADA41B92E006C3DA"& + "F1ED0FD63ADD9408A3FC815F727457692727637687C1F79D"& + "837DE20798E64C878181C02DF56A533F684459E8A03C8EF6"& + "234854531110E6CD9BDEFEA85E35C802B1ACDDF29C9332E2"& + "53C0FA72F3ED1ABA274838CFE6EF8BD572E89E1C2135F6A7"& + "5BC5D6EA4F85C9A757E68E561A56AC0FC19F1F086C43272F"; + + Put_Line("---sponge test---"); + Sponge(Input, Bitrate, Output); + Put_Line("Input is:"); + for I of Input loop + Put(Bit'Image(I)); + end loop; + new_line(1); + + Put_Line("Output is:"); + for I of Output loop + Put(Bit'Image(I)); + end loop; + new_line(1); + + Error := 0; + for I in 1..Output'Length/4 loop + Pos := Output'First + (I-1)*4; + C := Natural( Output( Pos ) ) * 8 + + Natural( Output( Pos + 1 ) ) * 4 + + Natural( Output( Pos + 2 ) ) * 2 + + Natural( Output( Pos + 3 ) ); + Hexstring(I) := Hex(C); + if Hexstring(I) /= ExpHex(I) then + Error := Error + 1; + end if; + end loop; + Put_Line("Expected: "); + Put_Line(ExpHex); + Put_Line("Obtained: "); + Put_Line(Hexstring); + Put_Line("Errors found: " & Natural'Image(Error)); + + end test_sponge; + + procedure test_keccak_function(T: in Test_Round) is + S: State; + begin + Put_Line("---Full Keccak Function test---"); + S := Keccak_Function(T(Round_Index'First)(None)); + if S /= T(Round_Index'Last)(Iota) then + Put_Line("FAILED: full keccak function test"); + else + Put_Line("PASSED: full keccak function test"); + end if; + end test_keccak_function; + -- end of helper methods --variables @@ -155,6 +283,9 @@ test_one_round(T(I), I); end loop; + -- test also Keccak_Function as a whole -- + test_keccak_function(T); + Put_Line("-----Testing with non-zero state as input------"); if (not read_from_file("testvectorsnonzero.txt", T)) then return; @@ -165,4 +296,13 @@ test_one_round(T(I), I); end loop; + -- test also Keccak_Function as a whole -- + test_keccak_function(T); + + -- test BitsToWord and WordToBits + test_bits_to_word_conversion; + + -- test Sponge construction + test_sponge; + end SMG_Keccak.Test;