1use crate::gadgets::{
2 file_hash_row_selector::{self, FileHashRowSelectorChip, FileHashRowSelectorConfig},
3 poseidon::{PoseidonChip, PoseidonConfig},
4};
5use halo2_gadgets::{poseidon::primitives::P128Pow5T3, utilities::Var};
6use halo2_proofs::{
7 circuit::{floor_planner::V1, Layouter, Value},
8 pasta::Fp,
9 plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Instance},
10};
11
12#[derive(Clone)]
13pub struct FileHashPartialConfig {
14 row_title_advice: Column<Advice>,
15 row_content_advice: Column<Advice>,
16 row_hash_advice: Column<Advice>,
17 instance: Column<Instance>,
18 poseidon_config: PoseidonConfig<3, 2, 2>,
19 row_selector_config: FileHashRowSelectorConfig,
20}
21
22pub struct FileHashPartialChip {
23 config: FileHashPartialConfig,
24}
25
26impl FileHashPartialChip {
27 pub fn construct(config: FileHashPartialConfig) -> Self {
28 Self { config }
29 }
30
31 pub fn configure(meta: &mut ConstraintSystem<Fp>) -> FileHashPartialConfig {
32 let row_content_advice = meta.advice_column();
33 let row_title_advice = meta.advice_column();
34 let row_hash_advice = meta.advice_column();
35 let instance = meta.instance_column();
36
37 meta.enable_equality(instance);
38
39 let state = (0..3).map(|_| meta.advice_column()).collect::<Vec<_>>();
40 for i in 0..3 {
41 meta.enable_equality(state[i]);
42 }
43
44 let poseidon_config = PoseidonChip::<P128Pow5T3, 3, 2, 2>::configure(meta, state);
45 let row_selector_config = FileHashRowSelectorChip::configure(meta);
46
47 FileHashPartialConfig {
48 row_title_advice,
49 row_content_advice,
50 row_hash_advice,
51 instance,
52 poseidon_config,
53 row_selector_config,
54 }
55 }
56}
57
58pub struct FileHashPartialCircuit<const L: usize> {
59 row_title: [Value<Fp>; L],
60 row_content: [Value<Fp>; L],
61 row_selectors: [Value<Fp>; L],
62}
63
64impl<const L: usize> Circuit<Fp> for FileHashPartialCircuit<L> {
65 type Config = FileHashPartialConfig;
66
67 type FloorPlanner = V1;
68
69 fn without_witnesses(&self) -> Self {
70 Self {
71 row_title: (0..L)
72 .map(|_i| Value::unknown())
73 .collect::<Vec<Value<Fp>>>()
74 .try_into()
75 .unwrap(),
76 row_content: (0..L)
77 .map(|_i| Value::unknown())
78 .collect::<Vec<Value<Fp>>>()
79 .try_into()
80 .unwrap(),
81 row_selectors: (0..L)
82 .map(|_i| Value::unknown())
83 .collect::<Vec<Value<Fp>>>()
84 .try_into()
85 .unwrap(),
86 }
87 }
88
89 fn configure(meta: &mut ConstraintSystem<Fp>) -> Self::Config {
90 FileHashPartialChip::configure(meta)
91 }
92
93 fn synthesize(
94 &self,
95 config: Self::Config,
96 mut layouter: impl halo2_proofs::circuit::Layouter<Fp>,
97 ) -> Result<(), halo2_proofs::plonk::Error> {
98 let poseidon_cs = PoseidonChip::<P128Pow5T3, 3, 2, 2>::construct(config.poseidon_config);
100
101 let mut file_hashes = Vec::new();
102 for i in 0..L {
103 let message = [self.row_title[i], self.row_content[i]];
104 let message_cells = poseidon_cs
105 .load_private_inputs(layouter.namespace(|| "load private inputs"), message)?;
106 let result =
107 poseidon_cs.hash(layouter.namespace(|| "poseidon chip"), &message_cells)?;
108 file_hashes.push(result);
109 }
110
111 let row_selector_cs = FileHashRowSelectorChip::<Fp>::construct(config.row_selector_config);
113
114 let mut selected_rows = Vec::new();
115 for i in 0..(file_hashes.len()) {
116 let (file_hash_cell, _, file_res_cell) = row_selector_cs.assign(
117 layouter.namespace(|| "row selectors"),
118 file_hashes[i].value().copied(),
119 self.row_selectors[i],
120 i,
121 )?;
122
123 layouter.assign_region(
124 || "selector equality",
125 |mut region| region.constrain_equal(file_hashes[i].cell(), file_hash_cell.cell()),
126 )?;
127
128 selected_rows.push(file_res_cell);
129 }
130
131 let starting_poseidon_hash_message = [file_hashes[0].clone(), file_hashes[1].clone()];
133 let mut accumulated_hash = poseidon_cs.hash(
134 layouter.namespace(|| "poseidon chip"),
135 &starting_poseidon_hash_message,
136 )?;
137
138 for i in 2..L {
139 let message_cells = [accumulated_hash.clone(), file_hashes[i].clone()];
140 accumulated_hash =
141 poseidon_cs.hash(layouter.namespace(|| "poseidon chip"), &message_cells)?;
142 }
143
144 layouter.constrain_instance(accumulated_hash.cell(), config.instance, 0)?;
145
146 Ok(())
147 }
148}
149
150#[cfg(test)]
151mod tests {
152 use halo2_gadgets::poseidon::primitives::{self as poseidon, ConstantLength, P128Pow5T3};
153 use halo2_proofs::{circuit::Value, dev::MockProver, pasta::Fp};
154
155 use super::FileHashPartialCircuit;
156
157 #[test]
158 fn test() {
159 let k = 8;
160 let row_title = [Fp::from(1), Fp::from(5)];
161 let row_content = [Fp::from(3), Fp::from(4)];
162 let row_selector = [Fp::from(0), Fp::from(1)];
163
164 let circuit = FileHashPartialCircuit::<2> {
165 row_title: row_title.map(|x| Value::known(x)),
166 row_content: row_content.map(|x| Value::known(x)),
167 row_selectors: row_selector.map(|x| Value::known(x)),
168 };
169
170 let mut row_hash = Vec::new();
171 for (&title, &content) in row_title.iter().zip(row_content.iter()) {
172 let message = [title, content];
173 let output =
174 poseidon::Hash::<_, P128Pow5T3, ConstantLength<2>, 3, 2>::init().hash(message);
175
176 row_hash.push(output);
177 }
178
179 let mut accumulator_hash = poseidon::Hash::<_, P128Pow5T3, ConstantLength<2>, 3, 2>::init()
180 .hash([row_hash[0], row_hash[1]]);
181
182 for i in 2..row_content.len() {
183 let message = [accumulator_hash, row_hash[i]];
184 let output =
185 poseidon::Hash::<_, P128Pow5T3, ConstantLength<2>, 3, 2>::init().hash(message);
186 accumulator_hash = output;
187 }
188
189 let pub_instance = vec![accumulator_hash];
190 let prover = MockProver::run(k, &circuit, vec![pub_instance]).unwrap();
191 prover.assert_satisfied();
192 }
193}