@@ -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,12 +31,26 @@ 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
36+ let mut block_array = convert_to_u64_array (block_bytes );
37+
38+ // Apply Keccak padding (0x01 after message, 0x80 at block end)
39+ apply_keccak_padding (&mut block_array , message_size , real_max_blocks );
40+
41+ let state = apply_keccak_permutations (block_array , real_max_blocks );
42+
43+ //3. sponge_squeeze
44+ read_hash_from_state (state )
45+ }
46+
47+ fn convert_to_u64_array <let N : u32 >(input : [u8 ; N ]) -> [u64 ; N / WORD_SIZE ] {
48+ static_assert (
49+ N % WORD_SIZE == 0 ,
50+ "Byte array is expected to cleanly divide into chunks" ,
51+ );
3652 // populate a vector of 64-bit limbs from our byte array
37- let mut sliced_buffer =
38- [0 ; (((N / BLOCK_SIZE_IN_BYTES ) + 1 ) * BLOCK_SIZE_IN_BYTES ) / WORD_SIZE ];
53+ let mut sliced_buffer = [0 ; N / WORD_SIZE ];
3954 for i in 0 ..sliced_buffer .len () {
4055 let limb_start = WORD_SIZE * i ;
4156
@@ -46,7 +61,7 @@ pub fn keccak256<let N: u32>(input: [u8; N], message_size: u32) -> [u8; 32] {
4661 WORD_SIZE ,
4762 |i : u32 | {
4863 quote {
49- sliced += v * (block_bytes [limb_start + $i ] as Field );
64+ sliced += v * (input [limb_start + $i ] as Field );
5065 v *= 256 ;
5166 }
5267 },
@@ -55,32 +70,36 @@ pub fn keccak256<let N: u32>(input: [u8; N], message_size: u32) -> [u8; 32] {
5570 sliced .assert_max_bit_size ::<64 >();
5671 sliced_buffer [i ] = sliced as u64 ;
5772 }
73+ sliced_buffer
74+ }
5875
59- // Apply Keccak padding (0x01 after message, 0x80 at block end)
60- apply_keccak_padding ( &mut sliced_buffer , message_size , real_max_blocks );
61-
76+ fn apply_keccak_permutations <let N : u32 >(
77+ flattened_blocks_array : [u64 ; N ],
78+ num_blocks : u32 ,
79+ ) -> [u64 ; NUM_KECCAK_LANES ] {
6280 //2. sponge_absorb
6381 let mut state : [u64 ; NUM_KECCAK_LANES ] = [0 ; NUM_KECCAK_LANES ];
64- // `real_max_blocks ` is guaranteed to at least be `1`
82+ // `num_blocks ` is guaranteed to at least be `1`
6583 // We peel out the first block as to avoid a conditional inside of the loop.
6684 // Otherwise, a dynamic predicate can cause a blowup in a constrained runtime.
6785 unroll_loop !(
6886 0 u32 ,
6987 LIMBS_PER_BLOCK ,
7088 |i : u32 | {
7189 quote {
72- state [$i ] = sliced_buffer [$i ];
90+ state [$i ] = flattened_blocks_array [$i ];
7391 }
7492 },
7593 );
7694 state = keccakf1600 (state );
7795
78- let state = if is_unconstrained () {
96+ let max_blocks = N / LIMBS_PER_BLOCK ;
97+ if is_unconstrained () {
7998 // When in an unconstrained runtime we can take advantage of runtime loop bounds,
8099 // thus allowing us to simplify the loop body.
81- for i in 1 ..real_max_blocks {
100+ for i in 1 ..num_blocks {
82101 for j in 0 ..LIMBS_PER_BLOCK {
83- state [j ] = state [j ] ^ sliced_buffer [i * LIMBS_PER_BLOCK + j ];
102+ state [j ] = state [j ] ^ flattened_blocks_array [i * LIMBS_PER_BLOCK + j ];
84103 }
85104 state = keccakf1600 (state );
86105 }
@@ -89,20 +108,40 @@ pub fn keccak256<let N: u32>(input: [u8; N], message_size: u32) -> [u8; 32] {
89108 } else {
90109 // We store the intermediate states in an array to avoid having a dynamic predicate
91110 // 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 ];
111+ let mut intermediate_states = [state ; N / LIMBS_PER_BLOCK + 1 ];
93112 for i in 1 ..max_blocks {
94113 let mut previous_state = intermediate_states [i - 1 ];
95114 for j in 0 ..LIMBS_PER_BLOCK {
96- previous_state [j ] = previous_state [j ] ^ sliced_buffer [i * LIMBS_PER_BLOCK + j ];
115+ previous_state [j ] =
116+ previous_state [j ] ^ flattened_blocks_array [i * LIMBS_PER_BLOCK + j ];
97117 }
98118 intermediate_states [i ] = keccakf1600 (previous_state );
99119 }
100120
101- // We can then take the state as of `real_max_blocks`, ignoring later permutations.
102- intermediate_states [real_max_blocks - 1 ]
103- };
121+ // We can then take the state as of `num_blocks`, ignoring later permutations.
122+ intermediate_states [num_blocks - 1 ]
123+ }
124+ }
104125
105- //3. sponge_squeeze
126+ // Apply Keccak padding to the u64 block array
127+ // Append 0x01 after message, then 0x80 at end of block
128+ // If both padding bytes collide at the same byte, combine them as 0x81
129+ #[inline_always]
130+ pub (crate ) fn apply_keccak_padding <let N : u32 >(
131+ block_array : &mut [u64 ; N ],
132+ message_size : u32 ,
133+ real_max_blocks : u32 ,
134+ ) {
135+ // Calculate limb index and byte offset within the limb (little-endian)
136+ let start_limb_index = message_size / WORD_SIZE ;
137+ let start_byte_offset = message_size % WORD_SIZE ;
138+
139+ block_array [start_limb_index ] += 0x01 << (8 * (start_byte_offset as u64 ));
140+ // The end padding byte (0x80) always goes at byte 7 of the last limb
141+ block_array [real_max_blocks * LIMBS_PER_BLOCK - 1 ] += 0x80 << 56 ;
142+ }
143+
144+ fn read_hash_from_state (state : [u64 ; NUM_KECCAK_LANES ]) -> [u8 ; 32 ] {
106145 let mut result = [0 ; 32 ];
107146 unroll_loop !(
108147 0 u32 ,
@@ -126,24 +165,6 @@ pub fn keccak256<let N: u32>(input: [u8; N], message_size: u32) -> [u8; 32] {
126165 result
127166}
128167
129- // Apply Keccak padding to the u64 block array
130- // Append 0x01 after message, then 0x80 at end of block
131- // If both padding bytes collide at the same byte, combine them as 0x81
132- #[inline_always]
133- pub (crate ) fn apply_keccak_padding <let N : u32 >(
134- block_array : &mut [u64 ; N ],
135- message_size : u32 ,
136- real_max_blocks : u32 ,
137- ) {
138- // Calculate limb index and byte offset within the limb (little-endian)
139- let start_limb_index = message_size / WORD_SIZE ;
140- let start_byte_offset = message_size % WORD_SIZE ;
141-
142- block_array [start_limb_index ] += 0x01 << (8 * (start_byte_offset as u64 ));
143- // The end padding byte (0x80) always goes at byte 7 of the last limb
144- block_array [real_max_blocks * LIMBS_PER_BLOCK - 1 ] += 0x80 << 56 ;
145- }
146-
147168comptime fn unroll_loop (start : u32 , end : u32 , body : fn (u32 ) -> Quoted ) -> Quoted {
148169 let mut iterations : [Quoted ] = &[];
149170 for i in start ..end {
0 commit comments