@@ -4,6 +4,7 @@ mod benchmarks;
44
55use std::hash::keccakf1600 ;
66use std::runtime::is_unconstrained ;
7+ use std::static_assert ;
78
89global BLOCK_SIZE_IN_BYTES : u32 = 136 ; //(1600 - BITS * 2) / WORD_SIZE;
910global WORD_SIZE : u32 = 8 ; // Limbs are made up of u64s so 8 bytes each.
@@ -30,15 +31,46 @@ pub fn keccak256<let N: u32>(input: [u8; N], message_size: u32) -> [u8; 32] {
3031 }
3132
3233 //1. format_input_lanes and apply padding
33- let max_blocks = (N + BLOCK_SIZE_IN_BYTES ) / BLOCK_SIZE_IN_BYTES ;
3434 let real_max_blocks = (message_size + BLOCK_SIZE_IN_BYTES ) / BLOCK_SIZE_IN_BYTES ;
3535
3636 // Apply Keccak padding (0x01 after message, 0x80 at block end)
3737 apply_keccak_padding (&mut block_bytes , message_size , real_max_blocks );
3838
39+ let block_array = convert_to_u64_array (block_bytes );
40+
41+ let state = apply_keccak_permutations (block_array , real_max_blocks );
42+
43+ //3. sponge_squeeze
44+ let mut result = [0 ; 32 ];
45+ unroll_loop !(
46+ 0 u32 ,
47+ 4 u32 ,
48+ |i : u32 | {
49+ quote {
50+ let lane = state [$i ] as Field ;
51+ let lane_le : [u8 ; 8 ] = lane .to_le_bytes ();
52+ result [8 * $i ] = lane_le [0 ];
53+ result [8 * $i + 1 ] = lane_le [1 ];
54+ result [8 * $i + 2 ] = lane_le [2 ];
55+ result [8 * $i + 3 ] = lane_le [3 ];
56+ result [8 * $i + 4 ] = lane_le [4 ];
57+ result [8 * $i + 5 ] = lane_le [5 ];
58+ result [8 * $i + 6 ] = lane_le [6 ];
59+ result [8 * $i + 7 ] = lane_le [7 ];
60+ }
61+ },
62+ );
63+
64+ result
65+ }
66+
67+ fn convert_to_u64_array <let N : u32 >(input : [u8 ; N ]) -> [u64 ; N / WORD_SIZE ] {
68+ static_assert (
69+ N % WORD_SIZE == 0 ,
70+ "Byte array is expected to cleanly divide into chunks" ,
71+ );
3972 // populate a vector of 64-bit limbs from our byte array
40- let mut sliced_buffer =
41- [0 ; (((N / BLOCK_SIZE_IN_BYTES ) + 1 ) * BLOCK_SIZE_IN_BYTES ) / WORD_SIZE ];
73+ let mut sliced_buffer = [0 ; N / WORD_SIZE ];
4274 for i in 0 ..sliced_buffer .len () {
4375 let limb_start = WORD_SIZE * i ;
4476
@@ -49,7 +81,7 @@ pub fn keccak256<let N: u32>(input: [u8; N], message_size: u32) -> [u8; 32] {
4981 WORD_SIZE ,
5082 |i : u32 | {
5183 quote {
52- sliced += v * (block_bytes [limb_start + $i ] as Field );
84+ sliced += v * (input [limb_start + $i ] as Field );
5385 v *= 256 ;
5486 }
5587 },
@@ -58,29 +90,36 @@ pub fn keccak256<let N: u32>(input: [u8; N], message_size: u32) -> [u8; 32] {
5890 sliced .assert_max_bit_size ::<64 >();
5991 sliced_buffer [i ] = sliced as u64 ;
6092 }
93+ sliced_buffer
94+ }
6195
96+ fn apply_keccak_permutations <let N : u32 >(
97+ flattened_blocks_array : [u64 ; N ],
98+ num_blocks : u32 ,
99+ ) -> [u64 ; NUM_KECCAK_LANES ] {
62100 //2. sponge_absorb
63101 let mut state : [u64 ; NUM_KECCAK_LANES ] = [0 ; NUM_KECCAK_LANES ];
64- // `real_max_blocks ` is guaranteed to at least be `1`
102+ // `num_blocks ` is guaranteed to at least be `1`
65103 // We peel out the first block as to avoid a conditional inside of the loop.
66104 // Otherwise, a dynamic predicate can cause a blowup in a constrained runtime.
67105 unroll_loop !(
68106 0 u32 ,
69107 LIMBS_PER_BLOCK ,
70108 |i : u32 | {
71109 quote {
72- state [$i ] = sliced_buffer [$i ];
110+ state [$i ] = flattened_blocks_array [$i ];
73111 }
74112 },
75113 );
76114 state = keccakf1600 (state );
77115
78- let state = if is_unconstrained () {
116+ let max_blocks = N / LIMBS_PER_BLOCK ;
117+ if is_unconstrained () {
79118 // When in an unconstrained runtime we can take advantage of runtime loop bounds,
80119 // thus allowing us to simplify the loop body.
81- for i in 1 ..real_max_blocks {
120+ for i in 1 ..num_blocks {
82121 for j in 0 ..LIMBS_PER_BLOCK {
83- state [j ] = state [j ] ^ sliced_buffer [i * LIMBS_PER_BLOCK + j ];
122+ state [j ] = state [j ] ^ flattened_blocks_array [i * LIMBS_PER_BLOCK + j ];
84123 }
85124 state = keccakf1600 (state );
86125 }
@@ -89,41 +128,18 @@ pub fn keccak256<let N: u32>(input: [u8; N], message_size: u32) -> [u8; 32] {
89128 } else {
90129 // We store the intermediate states in an array to avoid having a dynamic predicate
91130 // inside the loop, which can cause a blowup in a constrained runtime.
92- let mut intermediate_states = [state ; ( N + BLOCK_SIZE_IN_BYTES ) / BLOCK_SIZE_IN_BYTES + 1 ];
131+ let mut intermediate_states = [state ; N / LIMBS_PER_BLOCK + 1 ];
93132 for i in 1 ..max_blocks {
94133 let mut previous_state = intermediate_states [i - 1 ];
95134 for j in 0 ..LIMBS_PER_BLOCK {
96- previous_state [j ] = previous_state [j ] ^ sliced_buffer [i * LIMBS_PER_BLOCK + j ];
135+ previous_state [j ] = previous_state [j ] ^ flattened_blocks_array [i * LIMBS_PER_BLOCK + j ];
97136 }
98137 intermediate_states [i ] = keccakf1600 (previous_state );
99138 }
100139
101- // We can then take the state as of `real_max_blocks`, ignoring later permutations.
102- intermediate_states [real_max_blocks - 1 ]
103- };
104-
105- //3. sponge_squeeze
106- let mut result = [0 ; 32 ];
107- unroll_loop !(
108- 0 u32 ,
109- 4 u32 ,
110- |i : u32 | {
111- quote {
112- let lane = state [$i ] as Field ;
113- let lane_le : [u8 ; 8 ] = lane .to_le_bytes ();
114- result [8 * $i ] = lane_le [0 ];
115- result [8 * $i + 1 ] = lane_le [1 ];
116- result [8 * $i + 2 ] = lane_le [2 ];
117- result [8 * $i + 3 ] = lane_le [3 ];
118- result [8 * $i + 4 ] = lane_le [4 ];
119- result [8 * $i + 5 ] = lane_le [5 ];
120- result [8 * $i + 6 ] = lane_le [6 ];
121- result [8 * $i + 7 ] = lane_le [7 ];
122- }
123- },
124- );
125-
126- result
140+ // We can then take the state as of `num_blocks`, ignoring later permutations.
141+ intermediate_states [num_blocks - 1 ]
142+ }
127143}
128144
129145// Apply Keccak padding to the block_bytes array
@@ -147,7 +163,7 @@ pub(crate) fn apply_keccak_padding<let BLOCK_BYTES: u32>(
147163}
148164
149165comptime fn unroll_loop (start : u32 , end : u32 , body : fn (u32 ) -> Quoted ) -> Quoted {
150- let mut iterations : [Quoted ] = & [];
166+ let mut iterations : [Quoted ] = @ [];
151167 for i in start ..end {
152168 iterations = iterations .push_back (body (i ));
153169 }
0 commit comments