Skip to main content

binius_examples/circuits/
sha3.rs

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