Skip to main content

binius_examples/circuits/
sha256.rs

1// Copyright 2026 The Binius Developers
2// Copyright 2025 Irreducible Inc.
3use std::array;
4
5use anyhow::Result;
6use binius_circuits::{
7	fixed_byte_vec::ByteVec,
8	sha256::{sha256_fixed, sha256_varlen},
9};
10use binius_core::word::Word;
11use binius_frontend::{CircuitBuilder, Wire, WitnessFiller};
12use sha2::Digest;
13
14use super::utils::{self, HasherInstance, HasherMode, HasherParams};
15use crate::ExampleCircuit;
16
17pub struct Sha256Example {
18	circuit: Sha256Circuit,
19	mode: HasherMode,
20}
21
22/// Either the fixed-length (default) or variable-length SHA-256 circuit.
23enum Sha256Circuit {
24	/// Fixed-length gadget: message length is a compile-time constant.
25	Fixed {
26		message: Vec<Wire>,
27		digest: [Wire; 8],
28	},
29	/// Variable-length gadget: message length is a runtime witness.
30	Variable { message: ByteVec, digest: [Wire; 4] },
31}
32
33impl ExampleCircuit for Sha256Example {
34	type Params = HasherParams;
35	type Instance = HasherInstance;
36
37	fn build(params: HasherParams, builder: &mut CircuitBuilder) -> Result<Self> {
38		let mode = utils::resolve_hasher_mode(&params, "SHA-256", true)?;
39
40		let circuit = match mode {
41			// Fixed-length (default): message length is a compile-time constant.
42			HasherMode::Fixed { len_bytes } => {
43				let n_words = len_bytes.div_ceil(4);
44				let message: Vec<Wire> = (0..n_words).map(|_| builder.add_inout()).collect();
45				let computed_digest = sha256_fixed(builder, &message, len_bytes);
46				let digest: [Wire; 8] = array::from_fn(|_| builder.add_inout());
47				for i in 0..8 {
48					builder.assert_eq(format!("digest[{i}]"), computed_digest[i], digest[i]);
49				}
50				Sha256Circuit::Fixed { message, digest }
51			}
52			// Variable-length: message length is a runtime witness.
53			HasherMode::Variable { max_len_bytes } => {
54				let max_words = max_len_bytes.div_ceil(8);
55				let message = ByteVec::new_inout(builder, max_words);
56				let computed_digest = sha256_varlen(builder, &message);
57				let digest: [Wire; 4] = array::from_fn(|_| builder.add_inout());
58				for i in 0..4 {
59					builder.assert_eq(format!("digest[{i}]"), computed_digest[i], digest[i]);
60				}
61				Sha256Circuit::Variable { message, digest }
62			}
63		};
64
65		Ok(Self { circuit, mode })
66	}
67
68	fn populate_witness(&self, instance: HasherInstance, w: &mut WitnessFiller<'_>) -> Result<()> {
69		let message = utils::resolve_hasher_message(&self.mode, &instance)?;
70		let digest = sha2::Sha256::digest(&message);
71
72		match &self.circuit {
73			Sha256Circuit::Fixed {
74				message: message_wires,
75				digest: digest_wires,
76			} => {
77				// Message: 32-bit big-endian words, 4 bytes per wire, high 32 bits zero.
78				for (wire, word) in message_wires
79					.iter()
80					.zip(utils::pack_bytes_u32words(&message, true))
81				{
82					w[*wire] = word;
83				}
84				// Digest: 8 x 32-bit big-endian words.
85				for (i, chunk) in digest.chunks(4).enumerate() {
86					w[digest_wires[i]] = Word(u32::from_be_bytes(chunk.try_into().unwrap()) as u64);
87				}
88			}
89			Sha256Circuit::Variable {
90				message: byte_vec,
91				digest: digest_wires,
92			} => {
93				byte_vec.populate_data(w, &message);
94				byte_vec.populate_len_bytes(w, message.len());
95				for (i, chunk) in digest.chunks(8).enumerate() {
96					w[digest_wires[i]] = Word(u64::from_be_bytes(chunk.try_into().unwrap()));
97				}
98			}
99		}
100
101		Ok(())
102	}
103
104	fn param_summary(params: &Self::Params) -> Option<String> {
105		utils::hasher_param_summary(params)
106	}
107}