Skip to content

Commit d23e033

Browse files
committed
Merge branch 'main' into tf/apply-padding-to-block
2 parents fc926f5 + d61f392 commit d23e033

File tree

1 file changed

+57
-36
lines changed

1 file changed

+57
-36
lines changed

src/keccak256.nr

Lines changed: 57 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ mod benchmarks;
44

55
use std::hash::keccakf1600;
66
use std::runtime::is_unconstrained;
7+
use std::static_assert;
78

89
global BLOCK_SIZE_IN_BYTES: u32 = 136; //(1600 - BITS * 2) / WORD_SIZE;
910
global 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
0u32,
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
0u32,
@@ -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-
147168
comptime 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

Comments
 (0)