zkdoc_sdk/circuits/
file_hash_partial_v2.rs

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        // get all row hashes
99        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        // multiply by row_selector
112        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        // get final commitment
132        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}