cairo_vm/hint_processor/builtin_hint_processor/
sha256_utils.rs

1use crate::stdlib::{boxed::Box, collections::HashMap, prelude::*};
2
3use crate::Felt252;
4use crate::{
5    hint_processor::{
6        builtin_hint_processor::hint_utils::{
7            get_integer_from_var_name, get_ptr_from_var_name, insert_value_from_var_name,
8        },
9        hint_processor_utils::felt_to_u32,
10    },
11    serde::deserialize_program::ApTracking,
12    types::relocatable::MaybeRelocatable,
13    vm::errors::{hint_errors::HintError, vm_errors::VirtualMachineError},
14    vm::vm_core::VirtualMachine,
15};
16use generic_array::GenericArray;
17use num_traits::ToPrimitive;
18use sha2::compress256;
19
20use crate::hint_processor::hint_processor_definition::HintReference;
21
22use super::hint_utils::get_constant_from_var_name;
23
24const SHA256_STATE_SIZE_FELTS: usize = 8;
25const BLOCK_SIZE: usize = 7;
26const IV: [u32; SHA256_STATE_SIZE_FELTS] = [
27    0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19,
28];
29
30pub fn sha256_input(
31    vm: &mut VirtualMachine,
32    ids_data: &HashMap<String, HintReference>,
33    ap_tracking: &ApTracking,
34) -> Result<(), HintError> {
35    let n_bytes = get_integer_from_var_name("n_bytes", vm, ids_data, ap_tracking)?;
36    let n_bytes = n_bytes.as_ref();
37
38    insert_value_from_var_name(
39        "full_word",
40        if n_bytes >= &Felt252::from(4_i32) {
41            Felt252::ONE
42        } else {
43            Felt252::ZERO
44        },
45        vm,
46        ids_data,
47        ap_tracking,
48    )
49}
50
51/// Inner implementation of [`sha256_main_constant_input_length`] and [`sha256_main_arbitrary_input_length`]
52fn sha256_main(
53    vm: &mut VirtualMachine,
54    ids_data: &HashMap<String, HintReference>,
55    ap_tracking: &ApTracking,
56    constants: &HashMap<String, Felt252>,
57    iv: &mut [u32; 8],
58) -> Result<(), HintError> {
59    let input_ptr = get_ptr_from_var_name("sha256_start", vm, ids_data, ap_tracking)?;
60
61    // The original code gets it from `ids` in both cases, and this makes it easier
62    // to implement the arbitrary length one
63    let input_chunk_size_felts =
64        get_constant_from_var_name("SHA256_INPUT_CHUNK_SIZE_FELTS", constants)?
65            .to_usize()
66            .unwrap_or(100); // Hack: enough to fail the assertion
67
68    if input_chunk_size_felts >= 100 {
69        return Err(HintError::AssertionFailed(
70            "assert 0 <= _sha256_input_chunk_size_felts < 100"
71                .to_string()
72                .into_boxed_str(),
73        ));
74    }
75
76    let mut message: Vec<u8> = Vec::with_capacity(4 * input_chunk_size_felts);
77
78    for i in 0..input_chunk_size_felts {
79        let input_element = vm.get_integer((input_ptr + i)?)?;
80        let bytes = felt_to_u32(input_element.as_ref())?.to_be_bytes();
81        message.extend(bytes);
82    }
83
84    let new_message = GenericArray::clone_from_slice(&message);
85    compress256(iv, &[new_message]);
86
87    let mut output: Vec<MaybeRelocatable> = Vec::with_capacity(iv.len());
88
89    for new_state in iv {
90        output.push(Felt252::from(*new_state).into());
91    }
92
93    let output_base = get_ptr_from_var_name("output", vm, ids_data, ap_tracking)?;
94
95    vm.write_arg(output_base, &output)
96        .map_err(VirtualMachineError::Memory)?;
97    Ok(())
98}
99
100/* Implements hint:
101from starkware.cairo.common.cairo_sha256.sha256_utils import (
102    IV, compute_message_schedule, sha2_compress_function)
103
104_sha256_input_chunk_size_felts = int(ids.SHA256_INPUT_CHUNK_SIZE_FELTS)
105assert 0 <= _sha256_input_chunk_size_felts < 100
106
107w = compute_message_schedule(memory.get_range(
108    ids.sha256_start, _sha256_input_chunk_size_felts))
109new_state = sha2_compress_function(IV, w)
110segments.write_arg(ids.output, new_state)
111 */
112pub fn sha256_main_constant_input_length(
113    vm: &mut VirtualMachine,
114    ids_data: &HashMap<String, HintReference>,
115    ap_tracking: &ApTracking,
116    constants: &HashMap<String, Felt252>,
117) -> Result<(), HintError> {
118    let mut iv = IV;
119    sha256_main(vm, ids_data, ap_tracking, constants, &mut iv)
120}
121
122/* Implements hint:
123from starkware.cairo.common.cairo_sha256.sha256_utils import (
124    compute_message_schedule, sha2_compress_function)
125
126_sha256_input_chunk_size_felts = int(ids.SHA256_INPUT_CHUNK_SIZE_FELTS)
127assert 0 <= _sha256_input_chunk_size_felts < 100
128_sha256_state_size_felts = int(ids.SHA256_STATE_SIZE_FELTS)
129assert 0 <= _sha256_state_size_felts < 100
130w = compute_message_schedule(memory.get_range(
131    ids.sha256_start, _sha256_input_chunk_size_felts))
132new_state = sha2_compress_function(memory.get_range(ids.state, _sha256_state_size_felts), w)
133segments.write_arg(ids.output, new_state)
134 */
135pub fn sha256_main_arbitrary_input_length(
136    vm: &mut VirtualMachine,
137    ids_data: &HashMap<String, HintReference>,
138    ap_tracking: &ApTracking,
139    constants: &HashMap<String, Felt252>,
140) -> Result<(), HintError> {
141    let iv_ptr = get_ptr_from_var_name("state", vm, ids_data, ap_tracking)?;
142
143    let state_size_felt = get_constant_from_var_name("SHA256_STATE_SIZE_FELTS", constants)?;
144
145    let state_size = match state_size_felt.to_usize() {
146        Some(size) if size == SHA256_STATE_SIZE_FELTS => size,
147        // if size is valid, but not SHA256_STATE_SIZE_FELTS, throw error
148        // NOTE: in this case the python-vm fails with "not enough values to unpack" error
149        Some(size) if size < 100 => {
150            return Err(HintError::InvalidValue(Box::new((
151                "SHA256_STATE_SIZE_FELTS",
152                *state_size_felt,
153                Felt252::from(SHA256_STATE_SIZE_FELTS),
154            ))))
155        }
156        // otherwise, fails the assert
157        _ => {
158            return Err(HintError::AssertionFailed(
159                "assert 0 <= _sha256_state_size_felts < 100"
160                    .to_string()
161                    .into_boxed_str(),
162            ))
163        }
164    };
165
166    let mut iv = vm
167        .get_integer_range(iv_ptr, state_size)?
168        .into_iter()
169        .map(|x| felt_to_u32(x.as_ref()))
170        .collect::<Result<Vec<u32>, _>>()?
171        .try_into()
172        .expect("size is constant");
173
174    sha256_main(vm, ids_data, ap_tracking, constants, &mut iv)
175}
176
177pub fn sha256_finalize(
178    vm: &mut VirtualMachine,
179    ids_data: &HashMap<String, HintReference>,
180    ap_tracking: &ApTracking,
181) -> Result<(), HintError> {
182    let message: Vec<u8> = vec![0; 64];
183
184    let mut iv = IV;
185
186    let iv_static: Vec<MaybeRelocatable> = iv.iter().map(|n| Felt252::from(*n).into()).collect();
187
188    let new_message = GenericArray::clone_from_slice(&message);
189    compress256(&mut iv, &[new_message]);
190
191    let mut output: Vec<MaybeRelocatable> = Vec::with_capacity(SHA256_STATE_SIZE_FELTS);
192
193    for new_state in iv {
194        output.push(Felt252::from(new_state).into());
195    }
196
197    let sha256_ptr_end = get_ptr_from_var_name("sha256_ptr_end", vm, ids_data, ap_tracking)?;
198
199    let mut padding: Vec<MaybeRelocatable> = Vec::new();
200    let zero_vector_message: Vec<MaybeRelocatable> = vec![Felt252::ZERO.into(); 16];
201
202    for _ in 0..BLOCK_SIZE - 1 {
203        padding.extend_from_slice(zero_vector_message.as_slice());
204        padding.extend_from_slice(iv_static.as_slice());
205        padding.extend_from_slice(output.as_slice());
206    }
207
208    vm.write_arg(sha256_ptr_end, &padding)
209        .map_err(VirtualMachineError::Memory)?;
210    Ok(())
211}
212
213#[cfg(test)]
214mod tests {
215    use super::*;
216
217    use crate::{
218        any_box,
219        hint_processor::{
220            builtin_hint_processor::{
221                builtin_hint_processor_definition::{BuiltinHintProcessor, HintProcessorData},
222                hint_code,
223            },
224            hint_processor_definition::{HintProcessorLogic, HintReference},
225        },
226        utils::test_utils::*,
227        vm::vm_core::VirtualMachine,
228    };
229    use assert_matches::assert_matches;
230
231    use rstest::rstest;
232    #[cfg(target_arch = "wasm32")]
233    use wasm_bindgen_test::*;
234
235    const SHA256_INPUT_CHUNK_SIZE_FELTS: usize = 16;
236
237    #[test]
238    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
239    fn sha256_input_one() {
240        let mut vm = vm_with_range_check!();
241        vm.segments = segments![((1, 1), 7)];
242        vm.run_context.fp = 2;
243        let ids_data = ids_data!["full_word", "n_bytes"];
244        assert_matches!(sha256_input(&mut vm, &ids_data, &ApTracking::new()), Ok(()));
245
246        check_memory![vm.segments.memory, ((1, 0), 1)];
247    }
248
249    #[test]
250    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
251    fn sha256_input_zero() {
252        let mut vm = vm_with_range_check!();
253        vm.segments = segments![((1, 1), 3)];
254        vm.run_context.fp = 2;
255        let ids_data = ids_data!["full_word", "n_bytes"];
256        assert_matches!(sha256_input(&mut vm, &ids_data, &ApTracking::new()), Ok(()));
257
258        check_memory![vm.segments.memory, ((1, 0), 0)];
259    }
260
261    #[test]
262    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
263    fn sha256_constant_input_length_ok() {
264        let hint_code = hint_code::SHA256_MAIN_CONSTANT_INPUT_LENGTH;
265        let mut vm = vm_with_range_check!();
266
267        vm.segments = segments![
268            ((1, 0), (2, 0)),
269            ((1, 1), (3, 0)),
270            ((2, 0), 22),
271            ((2, 1), 22),
272            ((2, 2), 22),
273            ((2, 3), 22),
274            ((2, 4), 22),
275            ((2, 5), 22),
276            ((2, 6), 22),
277            ((2, 7), 22),
278            ((2, 8), 22),
279            ((2, 9), 22),
280            ((2, 10), 22),
281            ((2, 11), 22),
282            ((2, 12), 22),
283            ((2, 13), 22),
284            ((2, 14), 22),
285            ((2, 15), 22),
286            ((3, 9), 0)
287        ];
288        vm.run_context.fp = 2;
289        let ids_data = ids_data!["sha256_start", "output"];
290        let constants = HashMap::from([(
291            "SHA256_INPUT_CHUNK_SIZE_FELTS".to_string(),
292            Felt252::from(SHA256_INPUT_CHUNK_SIZE_FELTS),
293        )]);
294        assert_matches!(
295            run_hint!(&mut vm, ids_data, hint_code, exec_scopes_ref!(), &constants),
296            Ok(())
297        );
298
299        check_memory![
300            vm.segments.memory,
301            ((3, 0), 3704205499_u32),
302            ((3, 1), 2308112482_u32),
303            ((3, 2), 3022351583_u32),
304            ((3, 3), 174314172_u32),
305            ((3, 4), 1762869695_u32),
306            ((3, 5), 1649521060_u32),
307            ((3, 6), 2811202336_u32),
308            ((3, 7), 4231099170_u32)
309        ];
310    }
311
312    #[test]
313    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
314    fn sha256_arbitrary_input_length_ok() {
315        let hint_code = hint_code::SHA256_MAIN_ARBITRARY_INPUT_LENGTH;
316        let mut vm = vm_with_range_check!();
317
318        vm.segments = segments![
319            ((1, 0), (2, 0)),
320            ((1, 1), (3, 0)),
321            ((1, 2), (4, 0)),
322            ((2, 0), 22),
323            ((2, 1), 22),
324            ((2, 2), 22),
325            ((2, 3), 22),
326            ((2, 4), 22),
327            ((2, 5), 22),
328            ((2, 6), 22),
329            ((2, 7), 22),
330            ((2, 8), 22),
331            ((2, 9), 22),
332            ((2, 10), 22),
333            ((2, 11), 22),
334            ((2, 12), 22),
335            ((2, 13), 22),
336            ((2, 14), 22),
337            ((2, 15), 22),
338            ((3, 9), 0),
339            ((4, 0), 0x6A09E667),
340            ((4, 1), 0xBB67AE85),
341            ((4, 2), 0x3C6EF372),
342            ((4, 3), 0xA54FF53A),
343            ((4, 4), 0x510E527F),
344            ((4, 5), 0x9B05688C),
345            ((4, 6), 0x1F83D9AB),
346            ((4, 7), 0x5BE0CD18),
347        ];
348        vm.run_context.fp = 3;
349        let ids_data = ids_data!["sha256_start", "output", "state"];
350        let constants = HashMap::from([
351            (
352                "SHA256_INPUT_CHUNK_SIZE_FELTS".to_string(),
353                Felt252::from(SHA256_INPUT_CHUNK_SIZE_FELTS),
354            ),
355            (
356                "SHA256_STATE_SIZE_FELTS".to_string(),
357                Felt252::from(SHA256_STATE_SIZE_FELTS),
358            ),
359        ]);
360        assert_matches!(
361            run_hint!(&mut vm, ids_data, hint_code, exec_scopes_ref!(), &constants),
362            Ok(())
363        );
364        check_memory![
365            vm.segments.memory,
366            ((3, 0), 1676947577_u32),
367            ((3, 1), 1555161467_u32),
368            ((3, 2), 2679819371_u32),
369            ((3, 3), 2084775296_u32),
370            ((3, 4), 3059346845_u32),
371            ((3, 5), 785647811_u32),
372            ((3, 6), 2729325562_u32),
373            ((3, 7), 2503090120_u32)
374        ];
375    }
376
377    #[rstest]
378    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
379    #[case(hint_code::SHA256_MAIN_CONSTANT_INPUT_LENGTH)]
380    #[case(hint_code::SHA256_MAIN_ARBITRARY_INPUT_LENGTH)]
381    fn sha256_invalid_chunk_size(#[case] hint_code: &str) {
382        let mut vm = vm_with_range_check!();
383
384        vm.segments = segments![
385            ((1, 0), (2, 0)),
386            ((1, 1), (3, 0)),
387            ((1, 2), (4, 0)),
388            ((4, 0), 0x6A09E667),
389            ((4, 1), 0xBB67AE85),
390            ((4, 2), 0x3C6EF372),
391            ((4, 3), 0xA54FF53A),
392            ((4, 4), 0x510E527F),
393            ((4, 5), 0x9B05688C),
394            ((4, 6), 0x1F83D9AB),
395            ((4, 7), 0x5BE0CD18),
396        ];
397        vm.run_context.fp = 3;
398        let ids_data = ids_data!["sha256_start", "output", "state"];
399        let constants = HashMap::from([
400            (
401                "SHA256_INPUT_CHUNK_SIZE_FELTS".to_string(),
402                Felt252::from(100),
403            ),
404            (
405                "SHA256_STATE_SIZE_FELTS".to_string(),
406                Felt252::from(SHA256_STATE_SIZE_FELTS),
407            ),
408        ]);
409        assert_matches!(
410            run_hint!(&mut vm, ids_data, hint_code, exec_scopes_ref!(), &constants),
411            Err(HintError::AssertionFailed(bx)) if bx.as_ref() == "assert 0 <= _sha256_input_chunk_size_felts < 100"
412        );
413    }
414
415    #[test]
416    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
417    fn sha256_invalid_state_size() {
418        let hint_code = hint_code::SHA256_MAIN_ARBITRARY_INPUT_LENGTH;
419        let mut vm = vm_with_range_check!();
420
421        vm.segments = segments![
422            ((1, 0), (2, 0)),
423            ((1, 1), (3, 0)),
424            ((1, 2), (4, 0)),
425            ((4, 0), 0x6A09E667),
426            ((4, 1), 0xBB67AE85),
427            ((4, 2), 0x3C6EF372),
428            ((4, 3), 0xA54FF53A),
429            ((4, 4), 0x510E527F),
430            ((4, 5), 0x9B05688C),
431            ((4, 6), 0x1F83D9AB),
432            ((4, 7), 0x5BE0CD18),
433        ];
434        vm.run_context.fp = 3;
435        let ids_data = ids_data!["sha256_start", "output", "state"];
436        let constants = HashMap::from([
437            (
438                "SHA256_INPUT_CHUNK_SIZE_FELTS".to_string(),
439                Felt252::from(SHA256_INPUT_CHUNK_SIZE_FELTS),
440            ),
441            ("SHA256_STATE_SIZE_FELTS".to_string(), Felt252::from(100)),
442        ]);
443        assert_matches!(
444            run_hint!(&mut vm, ids_data, hint_code, exec_scopes_ref!(), &constants),
445            Err(HintError::AssertionFailed(bx)) if bx.as_ref() == "assert 0 <= _sha256_state_size_felts < 100"
446        );
447    }
448
449    #[test]
450    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
451    fn sha256_unexpected_state_size() {
452        let hint_code = hint_code::SHA256_MAIN_ARBITRARY_INPUT_LENGTH;
453        let state_size = Felt252::from(9);
454        let mut vm = vm_with_range_check!();
455
456        vm.segments = segments![
457            ((1, 0), (2, 0)),
458            ((1, 1), (3, 0)),
459            ((1, 2), (4, 0)),
460            ((4, 0), 0x6A09E667),
461            ((4, 1), 0xBB67AE85),
462            ((4, 2), 0x3C6EF372),
463            ((4, 3), 0xA54FF53A),
464            ((4, 4), 0x510E527F),
465            ((4, 5), 0x9B05688C),
466            ((4, 6), 0x1F83D9AB),
467            ((4, 7), 0x5BE0CD18),
468        ];
469        vm.run_context.fp = 3;
470        let ids_data = ids_data!["sha256_start", "output", "state"];
471        let constants = HashMap::from([
472            (
473                "SHA256_INPUT_CHUNK_SIZE_FELTS".to_string(),
474                Felt252::from(SHA256_INPUT_CHUNK_SIZE_FELTS),
475            ),
476            ("SHA256_STATE_SIZE_FELTS".to_string(), state_size),
477        ]);
478        let expected_size = Felt252::from(SHA256_STATE_SIZE_FELTS);
479        assert_matches!(
480            run_hint!(&mut vm, ids_data, hint_code, exec_scopes_ref!(), &constants),
481            Err(HintError::InvalidValue(bx))
482                if *bx == ("SHA256_STATE_SIZE_FELTS", state_size, expected_size)
483        );
484    }
485}