risc0_zkp/core/hash/sha/
rust_crypto.rs

1// Copyright 2024 RISC Zero, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! [Rust Crypto] wrappers for the RISC0 Sha256 trait.
16//!
17//! [Rust Crypto]: https://github.com/RustCrypto
18//!
19//! # Usage
20//!
21//! ```rust
22//! use risc0_zkp::core::hash::{
23//!     sha::{cpu, rust_crypto::{Sha256, Digest as _}},
24//! };
25//!
26//! // create a Sha256 object
27//! let mut hasher = Sha256::<cpu::Impl>::new();
28//!
29//! // write input message
30//! hasher.update(b"hello world");
31//!
32//! // read hash digest and consume hasher
33//! let result = hasher.finalize();
34//!
35//! assert_eq!(hex::encode(result), "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9");
36//!
37//! // more concise version of the code above.
38//! assert_eq!(hex::encode(Sha256::<cpu::Impl>::digest(b"hello world")),
39//!     "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"
40//! );
41//! ```
42
43use alloc::{format, vec::Vec};
44use core::fmt::{Debug, Formatter};
45
46use digest::{
47    block_buffer::Eager,
48    const_oid::{AssociatedOid, ObjectIdentifier},
49    core_api::{
50        AlgorithmName, Block, BlockSizeUser, Buffer, BufferKindUser, CoreWrapper,
51        CtVariableCoreWrapper, OutputSizeUser, TruncSide, UpdateCore, VariableOutputCore,
52    },
53    typenum::{U32, U64},
54    HashMarker, InvalidOutputSize,
55};
56pub use digest::{Digest, Output};
57
58use super::{BLOCK_BYTES, SHA256_INIT};
59
60/// Core block-level SHA-256 hasher with variable output size.
61///
62/// Supports initialization only for the 32 byte output size.
63#[derive(Clone)]
64pub struct Sha256VarCore<S: super::Sha256> {
65    // Current internal state of the SHA-256 hashing operation.
66    state: Option<S::DigestPtr>,
67    // Counter of the number of blocks hashed so far. Note that with blocks of 64 bytes, this
68    // implies that a maximum of 256 GB can be hashed.
69    block_len: u32,
70}
71
72impl<S: super::Sha256> HashMarker for Sha256VarCore<S> {}
73
74impl<S: super::Sha256> BlockSizeUser for Sha256VarCore<S> {
75    type BlockSize = U64;
76}
77
78impl<S: super::Sha256> BufferKindUser for Sha256VarCore<S> {
79    type BufferKind = Eager;
80}
81
82impl<S: super::Sha256> UpdateCore for Sha256VarCore<S> {
83    #[inline]
84    fn update_blocks(&mut self, blocks: &[Block<Self>]) {
85        self.block_len += u32::try_from(blocks.len()).unwrap();
86
87        // If aligned, reinterpret the u8 array blocks as u32 array blocks.
88        // If unaligned, the data needs to be copied.
89        // NOTE: The current implementation makes a full-copy of the blocks on the heap
90        // when the input is not word-aligned. Although a copy is needed,
91        // this could be done (slightly less efficiently in the zkVM) by
92        // copying to a fixed width buffer and incrementally hashing.
93        // SAFETY: We know that Block (alias for
94        // GenericArray<u8, U64>) is an array of bytes and so is safe to
95        // reinterpret as blocks of words.
96        let current_state = self.state.as_deref().unwrap_or(&SHA256_INIT);
97        self.state = Some(match unsafe { blocks.align_to::<super::Block>() } {
98            (&[], aligned_blocks, &[]) => S::compress_slice(current_state, aligned_blocks),
99            _ => S::compress_slice(
100                current_state,
101                &blocks
102                    .iter()
103                    .map(|block| bytemuck::pod_read_unaligned(block.as_slice()))
104                    .collect::<Vec<_>>(),
105            ),
106        });
107    }
108}
109
110impl<S: super::Sha256> OutputSizeUser for Sha256VarCore<S> {
111    type OutputSize = U32;
112}
113
114impl<S: super::Sha256> VariableOutputCore for Sha256VarCore<S> {
115    const TRUNC_SIDE: TruncSide = TruncSide::Left;
116
117    #[inline]
118    fn new(output_size: usize) -> Result<Self, InvalidOutputSize> {
119        let state = match output_size {
120            32 => None,
121            _ => return Err(InvalidOutputSize),
122        };
123        let block_len = 0;
124        Ok(Self { state, block_len })
125    }
126
127    #[inline]
128    fn finalize_variable_core(&mut self, buffer: &mut Buffer<Self>, out: &mut Output<Self>) {
129        let bit_len =
130            8 * (u32::try_from(buffer.get_pos()).unwrap() + (BLOCK_BYTES as u32) * self.block_len);
131        buffer.len64_padding_be(bit_len as u64, |block| {
132            // If aligned, reinterpret the u8 array blocks as a u32 array block.
133            // If unaligned, the data needs to be copied.
134            let current_state = self.state.as_deref().unwrap_or(&SHA256_INIT);
135            self.state = Some(
136                match bytemuck::try_from_bytes::<super::Block>(block.as_slice()) {
137                    Ok(b) => S::compress(current_state, b.as_half_blocks().0, b.as_half_blocks().1),
138                    Err(_) => {
139                        let b: super::Block = bytemuck::pod_read_unaligned(block.as_slice());
140                        S::compress(current_state, b.as_half_blocks().0, b.as_half_blocks().1)
141                    }
142                },
143            );
144        });
145
146        // NOTE: With some work, it would be possible to eliminate the copy here.
147        // However it will require quite a bit of reworking of the APIs
148        // involved and this copy may be optimized out by the compiler
149        // anyway.
150        out.copy_from_slice(self.state.as_deref().unwrap().as_bytes())
151    }
152}
153
154impl<S: super::Sha256> AlgorithmName for Sha256VarCore<S> {
155    #[inline]
156    fn write_alg_name(f: &mut Formatter<'_>) -> core::fmt::Result {
157        f.write_str("Sha256")
158    }
159}
160
161impl<S: super::Sha256> Debug for Sha256VarCore<S> {
162    #[inline]
163    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
164        f.write_str(&format!(
165            "Sha256VarCore<{}> {{ ... }}",
166            core::any::type_name::<S>()
167        ))
168    }
169}
170
171#[doc(hidden)]
172#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
173pub struct OidSha256;
174
175impl AssociatedOid for OidSha256 {
176    const OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("2.16.840.1.101.3.4.2.1");
177}
178
179/// SHA-256 implementation cross-compatible with `sha2::Sha256`.
180pub type Sha256<S> = CoreWrapper<CtVariableCoreWrapper<Sha256VarCore<S>, U32, OidSha256>>;