Skip to main content

midnight_circuits/hash/sha256/
mod.rs

1// This file is part of MIDNIGHT-ZK.
2// Copyright (C) Midnight Foundation
3// SPDX-License-Identifier: Apache-2.0
4// Licensed under the Apache License, Version 2.0 (the "License");
5// You may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7// http://www.apache.org/licenses/LICENSE-2.0
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14//! Implementation in-circuit of the SHA256 hash function.
15
16#![allow(non_snake_case)]
17
18mod sha256_chip;
19mod sha256_varlen;
20mod types;
21pub(crate) mod utils;
22
23use midnight_proofs::{circuit::Layouter, plonk::Error};
24use sha2::Digest;
25pub use sha256_chip::{Sha256Chip, Sha256Config, NB_SHA256_ADVICE_COLS, NB_SHA256_FIXED_COLS};
26pub use sha256_varlen::VarLenSha256Gadget;
27
28use crate::{
29    instructions::{
30        hash::{HashCPU, VarHashInstructions},
31        DecompositionInstructions, HashInstructions,
32    },
33    types::AssignedByte,
34    vec::AssignedVector,
35    CircuitField,
36};
37
38impl<F: CircuitField> HashCPU<u8, [u8; 32]> for Sha256Chip<F> {
39    fn hash(inputs: &[u8]) -> [u8; 32] {
40        let output = sha2::Sha256::digest(inputs);
41        output.into_iter().collect::<Vec<_>>().try_into().unwrap()
42    }
43}
44
45impl<F: CircuitField> HashInstructions<F, AssignedByte<F>, [AssignedByte<F>; 32]>
46    for Sha256Chip<F>
47{
48    fn hash(
49        &self,
50        layouter: &mut impl Layouter<F>,
51        inputs: &[AssignedByte<F>],
52    ) -> Result<[AssignedByte<F>; 32], Error> {
53        let mut output_bytes = Vec::with_capacity(32);
54
55        // We convert each `AssignedPlain<32>` returned by `self.sha256` into 4 bytes.
56        for word in self.sha256(layouter, inputs)? {
57            let bytes = self.native_gadget.assigned_to_be_bytes(layouter, &word.0, Some(4))?;
58            output_bytes.extend(bytes)
59        }
60
61        Ok(output_bytes.try_into().unwrap())
62    }
63}
64
65impl<F: CircuitField> HashCPU<u8, [u8; 32]> for VarLenSha256Gadget<F> {
66    fn hash(inputs: &[u8]) -> [u8; 32] {
67        let output = sha2::Sha256::digest(inputs);
68        output.into_iter().collect::<Vec<_>>().try_into().unwrap()
69    }
70}
71
72impl<F: CircuitField, const MAX_LEN: usize>
73    VarHashInstructions<F, MAX_LEN, AssignedByte<F>, [AssignedByte<F>; 32], 64>
74    for VarLenSha256Gadget<F>
75{
76    fn varhash(
77        &self,
78        layouter: &mut impl Layouter<F>,
79        inputs: &AssignedVector<F, AssignedByte<F>, MAX_LEN, 64>,
80    ) -> Result<[AssignedByte<F>; 32], Error> {
81        let mut output_bytes = Vec::with_capacity(32);
82
83        // We convert each `AssignedPlain<32>` returned by `self.sha256_varlen` into 4
84        // bytes.
85        for word in self.sha256_varlen(layouter, inputs)? {
86            let bytes =
87                self.sha256chip.native_gadget.assigned_to_be_bytes(layouter, &word.0, Some(4))?;
88            output_bytes.extend(bytes)
89        }
90
91        Ok(output_bytes.try_into().unwrap())
92    }
93}
94
95#[cfg(test)]
96mod tests {
97    use midnight_curves::Fq as Scalar;
98
99    use super::sha256_varlen::VarLenSha256Gadget;
100    use crate::{
101        field::NativeGadget,
102        hash::sha256::Sha256Chip,
103        instructions::hash::tests::{test_hash, test_varhash},
104        types::AssignedByte,
105    };
106
107    #[test]
108    fn test_sha256_hash() {
109        fn test_wrapper(input_size: usize, cost_model: bool) {
110            test_hash::<
111                Scalar,
112                AssignedByte<Scalar>,
113                [AssignedByte<Scalar>; 32],
114                Sha256Chip<Scalar>,
115                NativeGadget<Scalar, _, _>,
116            >(cost_model, "SHA256", input_size)
117        }
118
119        const SHA256_BLOCK_SIZE: usize = 64;
120        const SHA256_EDGE_PADDING: usize = 55;
121
122        // Cost model updat with input size = 256
123        test_wrapper(4 * SHA256_BLOCK_SIZE, true);
124
125        test_wrapper(SHA256_BLOCK_SIZE, false);
126        test_wrapper(SHA256_BLOCK_SIZE - 1, false);
127        test_wrapper(SHA256_BLOCK_SIZE - 2, false);
128        test_wrapper(2 * SHA256_BLOCK_SIZE, false);
129
130        test_wrapper(SHA256_EDGE_PADDING, false);
131        test_wrapper(SHA256_EDGE_PADDING - 1, false);
132
133        test_wrapper(0, false);
134        test_wrapper(1, false);
135        test_wrapper(2, false);
136    }
137
138    #[test]
139    fn test_sha256_varhash() {
140        fn test_wrapper<const M: usize>(input_size: usize, cost_model: bool) {
141            test_varhash::<
142                Scalar,
143                AssignedByte<Scalar>,
144                [AssignedByte<Scalar>; 32],
145                VarLenSha256Gadget<Scalar>,
146                M,
147                64,
148            >(cost_model, "VarSHA256", input_size)
149        }
150
151        test_wrapper::<512>(64, false);
152        test_wrapper::<512>(63, false);
153
154        // Cost model update with input size = 256
155        test_wrapper::<256>(128, true);
156        test_wrapper::<256>(127, false);
157
158        test_wrapper::<128>(55, false); // padding edge cases
159        test_wrapper::<128>(56, false);
160
161        test_wrapper::<128>(0, false);
162        test_wrapper::<128>(1, false);
163    }
164}