Skip to content

Commit aac1edd

Browse files
chore: handle last block message in process_full_blocks (#35)
Co-authored-by: Tom French <[email protected]>
1 parent d398f9d commit aac1edd

File tree

4 files changed

+24
-44
lines changed

4 files changed

+24
-44
lines changed

.github/workflows/benchmark.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
- name: Install Nargo
1818
uses: noir-lang/[email protected]
1919
with:
20-
toolchain: nightly-2025-08-15
20+
toolchain: 1.0.0-beta.11
2121

2222
- name: Install bb
2323
run: |

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ on:
88

99
env:
1010
CARGO_TERM_COLOR: always
11-
MINIMUM_NOIR_VERSION: nightly-2025-08-15
11+
MINIMUM_NOIR_VERSION: 1.0.0-beta.11
1212

1313
jobs:
1414
noir-version-list:

src/sha224.nr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ pub fn sha224_var<let N: u32>(msg: [u8; N], message_size: u64) -> sha224_constan
2121
let (mut h, mut msg_block) =
2222
process_full_blocks(msg, message_size, sha224_constants::INITIAL_STATE_SHA224);
2323

24-
let hash: HASH = finalize_sha256_blocks(msg, message_size, N, h, msg_block);
24+
let hash: HASH = finalize_sha256_blocks::<N>(message_size, h, msg_block);
2525
// Convert 32-byte hash to 28-byte hash for SHA-224
2626
let mut hash_sha224: sha224_constants::HASH_SHA224 = [0; 28];
2727
for i in 0..28 {

src/sha256.nr

Lines changed: 21 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ pub fn sha256_var<let N: u32>(msg: [u8; N], message_size: u64) -> HASH {
3939
} else {
4040
let (mut h, mut msg_block) = process_full_blocks(msg, message_size, INITIAL_STATE);
4141

42-
finalize_sha256_blocks(msg, message_size, N, h, msg_block)
42+
finalize_sha256_blocks::<N>(message_size, h, msg_block)
4343
}
4444
}
4545

@@ -119,14 +119,14 @@ pub(crate) fn process_full_blocks<let N: u32>(
119119
// Consider a message with an unknown number of bytes, `msg_size. It can be seen that this will have `msg_size / BLOCK_SIZE` full blocks.
120120
// - `states[i]` should then be the state after processing the first `i` blocks.
121121
// - `blocks[i]` should then be the next message block after processing the first `i` blocks.
122+
// blocks[first_partially_filled_block_index] is the last block that is partially filled or all 0 if the message is a multiple of the block size.
122123
//
123124
// In other words:
124125
//
125-
// blocks = [block 1, block 2, ..., block N / BLOCK_SIZE, empty block]
126+
// blocks = [block 1, block 2, ..., block N / BLOCK_SIZE, block N / BLOCK_SIZE + 1]
126127
// states = [INITIAL_STATE, state after block 1, state after block 2, ..., state after block N / BLOCK_SIZE]
127128
//
128129
// We place the initial state in `states[0]` as in the case where the `message_size < BLOCK_SIZE` then there are no full blocks to process and no compressions should occur.
129-
// Similarly, we place an empty block in `blocks[N/BLOCK_SIZE]` as in the case where the `message_size == N` then the padding bits will be written to an empty block.
130130
let mut blocks: [MSG_BLOCK; N / BLOCK_SIZE + 1] = std::mem::zeroed();
131131
let mut states: [STATE; N / BLOCK_SIZE + 1] = [h; N / BLOCK_SIZE + 1];
132132

@@ -152,8 +152,22 @@ pub(crate) fn process_full_blocks<let N: u32>(
152152
blocks[i] = new_msg_block;
153153
states[i + 1] = sha256_compression(new_msg_block, states[i]);
154154
}
155+
// If message_size/BLOCK_SIZE == N/BLOCK_SIZE, and there is a remainder, we need to process the last block.
156+
if N % BLOCK_SIZE != 0 {
157+
let new_msg_block =
158+
// Safety: separate verification function
159+
unsafe { build_msg_block(msg, message_size, BLOCK_SIZE * num_blocks) };
160+
161+
// Verify the block we are compressing was appropriately constructed
162+
verify_msg_block(msg, message_size, new_msg_block, BLOCK_SIZE * num_blocks);
163+
164+
blocks[num_blocks] = new_msg_block;
165+
}
155166

156-
(states[first_partially_filled_block_index], blocks[first_partially_filled_block_index])
167+
// verify the 0 padding is correct for the last block
168+
let final_block = blocks[first_partially_filled_block_index];
169+
verify_msg_block_zeros(final_block, message_size % BLOCK_SIZE, INT_BLOCK_SIZE);
170+
(states[first_partially_filled_block_index], final_block)
157171
}
158172

159173
// Take `BLOCK_SIZE` number of bytes from `msg` starting at `msg_start` and pack them into a `MSG_BLOCK`.
@@ -241,13 +255,6 @@ fn verify_msg_block<let N: u32>(
241255
}
242256
}
243257

244-
// Verify the block we are compressing was appropriately padded with zeros by `build_msg_block`.
245-
// This is only relevant for the last, potentially partially filled block.
246-
fn verify_msg_block_padding(msg_block: MSG_BLOCK, msg_byte_ptr: BLOCK_BYTE_PTR) {
247-
// Check all the way to the end of the block.
248-
verify_msg_block_zeros(msg_block, msg_byte_ptr, INT_BLOCK_SIZE);
249-
}
250-
251258
// Verify that a region of ints in the message block are (partially) zeroed,
252259
// up to an (exclusive) maximum which can either be the end of the block
253260
// or just where the size is to be written.
@@ -256,11 +263,6 @@ fn verify_msg_block_zeros(
256263
mut msg_byte_ptr: BLOCK_BYTE_PTR,
257264
max_int_byte_ptr: u32,
258265
) {
259-
// This variable is used to get around the compiler under-constrained check giving a warning.
260-
// We want to check against a constant zero, but if it does not come from the circuit inputs
261-
// or return values the compiler check will issue a warning.
262-
let zero = msg_block[0] - msg_block[0];
263-
264266
// First integer which is supposed to be (partially) zero.
265267
let mut int_byte_ptr = msg_byte_ptr / INT_SIZE;
266268

@@ -275,14 +277,14 @@ fn verify_msg_block_zeros(
275277
} else {
276278
TWO_POW_8
277279
};
278-
assert_eq(msg_block[int_byte_ptr] % mask, zero);
280+
assert_eq(msg_block[int_byte_ptr] % mask, 0);
279281
int_byte_ptr = int_byte_ptr + 1;
280282
}
281283

282284
// Check the rest of the items.
283285
for i in 0..max_int_byte_ptr {
284286
if i >= int_byte_ptr {
285-
assert_eq(msg_block[i], zero);
287+
assert_eq(msg_block[i], 0);
286288
}
287289
}
288290
}
@@ -499,33 +501,11 @@ fn hash_final_block(msg_block: MSG_BLOCK, mut state: STATE) -> HASH {
499501
}
500502

501503
pub(crate) fn finalize_sha256_blocks<let N: u32>(
502-
msg: [u8; N],
503504
message_size: u32,
504-
total_len: u32,
505505
mut h: STATE,
506506
mut msg_block: MSG_BLOCK,
507507
) -> HASH {
508508
let mut msg_byte_ptr = message_size % BLOCK_SIZE;
509-
let modulo = total_len % BLOCK_SIZE;
510-
// Handle setup of the final msg block.
511-
// This case is only hit if the msg is less than the block size,
512-
// or our message cannot be evenly split into blocks.
513-
if modulo != 0 {
514-
let num_blocks = total_len / BLOCK_SIZE;
515-
let msg_start = BLOCK_SIZE * num_blocks;
516-
let new_msg_block =
517-
// Safety: separate verification function
518-
unsafe { build_msg_block(msg, message_size, msg_start) };
519-
520-
if msg_start < message_size {
521-
msg_block = new_msg_block;
522-
}
523-
524-
verify_msg_block(msg, message_size, msg_block, msg_start);
525-
if msg_start < message_size {
526-
verify_msg_block_padding(msg_block, msg_byte_ptr);
527-
}
528-
}
529509

530510
// If we had modulo == 0 then it means the last block was full,
531511
// and we can reset the pointer to zero to overwrite it.
@@ -617,7 +597,7 @@ pub fn partial_sha256_var_end<let N: u32>(
617597
}
618598
} else {
619599
let (mut h, mut msg_block) = process_full_blocks(msg, message_size, h);
620-
finalize_sha256_blocks(msg, real_message_size, N, h, msg_block)
600+
finalize_sha256_blocks::<N>(real_message_size, h, msg_block)
621601
}
622602
}
623603

0 commit comments

Comments
 (0)