Skip to content

Commit cc9b20a

Browse files
committed
[WIP] feat: implement record circuit
1 parent 5a8a99a commit cc9b20a

File tree

2 files changed

+304
-0
lines changed

2 files changed

+304
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -1 +1,4 @@
11
pub(crate) mod public_inputs;
2+
pub(crate) mod record;
3+
4+
use public_inputs::PublicInputs;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,301 @@
1+
use crate::results_tree::extraction::PublicInputs;
2+
use alloy::primitives::U256;
3+
use mp2_common::{
4+
group_hashing::CircuitBuilderGroupHashing,
5+
poseidon::{empty_poseidon_hash, H},
6+
public_inputs::PublicInputCommon,
7+
serialization::{deserialize, deserialize_long_array, serialize, serialize_long_array},
8+
types::CBuilder,
9+
u256::{CircuitBuilderU256, UInt256Target, WitnessWriteU256},
10+
utils::{SelectHashBuilder, ToTargets},
11+
D, F,
12+
};
13+
use plonky2::{
14+
hash::hash_types::{HashOut, HashOutTarget},
15+
iop::{
16+
target::{BoolTarget, Target},
17+
witness::{PartialWitness, WitnessWrite},
18+
},
19+
plonk::proof::ProofWithPublicInputsTarget,
20+
};
21+
use recursion_framework::circuit_builder::CircuitLogicWires;
22+
use serde::{Deserialize, Serialize};
23+
use std::iter;
24+
25+
#[derive(Clone, Debug, Serialize, Deserialize)]
26+
pub struct RecordWires<const MAX_NUM_RESULTS: usize> {
27+
#[serde(
28+
serialize_with = "serialize_long_array",
29+
deserialize_with = "deserialize_long_array"
30+
)]
31+
indexed_items: [UInt256Target; MAX_NUM_RESULTS],
32+
#[serde(
33+
serialize_with = "serialize_long_array",
34+
deserialize_with = "deserialize_long_array"
35+
)]
36+
index_ids: [Target; 2],
37+
#[serde(serialize_with = "serialize", deserialize_with = "deserialize")]
38+
tree_hash: HashOutTarget,
39+
counter: Target,
40+
#[serde(serialize_with = "serialize", deserialize_with = "deserialize")]
41+
is_stored_in_leaf: BoolTarget,
42+
offset_range_min: UInt256Target,
43+
offset_range_max: UInt256Target,
44+
}
45+
46+
#[derive(Clone, Debug, Serialize, Deserialize)]
47+
pub struct RecordCircuit<const MAX_NUM_RESULTS: usize> {
48+
/// Values of the indexed items for in this record;
49+
/// if there is no secondary indexed item, just place the dummy value `0`
50+
#[serde(
51+
serialize_with = "serialize_long_array",
52+
deserialize_with = "deserialize_long_array"
53+
)]
54+
pub(crate) indexed_items: [U256; MAX_NUM_RESULTS],
55+
/// Integer identifiers of the indexed items
56+
#[serde(
57+
serialize_with = "serialize_long_array",
58+
deserialize_with = "deserialize_long_array"
59+
)]
60+
pub(crate) index_ids: [F; 2],
61+
/// Hash of the cells tree for this record;
62+
/// could be empty hash if there is no cells tree for this record
63+
pub(crate) tree_hash: HashOut<F>,
64+
/// Counter for the node associated to this record
65+
pub(crate) counter: F,
66+
/// Boolean flag specifying whether this record is stored
67+
/// in a leaf node of a rows tree or not
68+
pub(crate) is_stored_in_leaf: bool,
69+
/// Minimum offset range bound
70+
pub(crate) offset_range_min: U256,
71+
/// Maximum offset range bound
72+
pub(crate) offset_range_max: U256,
73+
}
74+
75+
impl<const MAX_NUM_RESULTS: usize> RecordCircuit<MAX_NUM_RESULTS> {
76+
pub fn build(b: &mut CBuilder) -> RecordWires<MAX_NUM_RESULTS> {
77+
let ffalse = b._false();
78+
let empty_hash = b.constant_hash(*empty_poseidon_hash());
79+
80+
let indexed_items: [UInt256Target; MAX_NUM_RESULTS] = b.add_virtual_u256_arr_unsafe();
81+
let index_ids: [Target; 2] = b.add_virtual_target_arr();
82+
let mut tree_hash = b.add_virtual_hash();
83+
let counter = b.add_virtual_target();
84+
let is_stored_in_leaf = b.add_virtual_bool_target_safe();
85+
let offset_range_min = b.add_virtual_u256_unsafe();
86+
let offset_range_max = b.add_virtual_u256_unsafe();
87+
88+
// H(H("")||H("")||indexed_items[1]||indexed_items[1]||index_ids[1]||indexed_items[1]||tree_hash)
89+
let tree_hash_inputs = empty_hash
90+
.elements
91+
.iter()
92+
.cloned()
93+
.chain(empty_hash.elements)
94+
.chain(indexed_items[1].to_targets())
95+
.chain(indexed_items[1].to_targets())
96+
.chain(iter::once(index_ids[1]))
97+
.chain(indexed_items[1].to_targets())
98+
.chain(tree_hash.elements)
99+
.collect();
100+
let new_tree_hash = b.hash_n_to_hash_no_pad::<H>(tree_hash_inputs);
101+
tree_hash = b.select_hash(is_stored_in_leaf, &new_tree_hash, &tree_hash);
102+
103+
// D(index_ids[0]||indexed_items[0]||index_ids[1]||indexed_items[1]||tree_hash)
104+
let accumulator_inputs: Vec<_> = iter::once(index_ids[0])
105+
.chain(indexed_items[0].to_targets())
106+
.chain(iter::once(index_ids[1]))
107+
.chain(indexed_items[1].to_targets())
108+
.chain(tree_hash.to_targets())
109+
.collect();
110+
let accumulator = b.map_to_curve_point(&accumulator_inputs);
111+
112+
// Ensure the counter associated to the current record is in the range
113+
// specified by the query
114+
// offset_range_min <= counter <= offset_range_max
115+
// -> NOT((counter < offset_range_min) OR (counter > offset_range_max)
116+
let counter_u256 = UInt256Target::new_from_target(b, counter);
117+
let is_less_than = b.is_less_than_u256(&counter_u256, &offset_range_min);
118+
let is_greater_than = b.is_greater_than_u256(&counter_u256, &offset_range_max);
119+
let is_out_of_range = b.or(is_less_than, is_greater_than);
120+
b.connect(is_out_of_range.target, ffalse.target);
121+
122+
// Register the public inputs.
123+
PublicInputs::<_, MAX_NUM_RESULTS>::new(
124+
&tree_hash.to_targets(),
125+
&indexed_items[1].to_targets(),
126+
&indexed_items[1].to_targets(),
127+
&indexed_items[0].to_targets(),
128+
&index_ids,
129+
&[counter],
130+
&[counter],
131+
&offset_range_min.to_targets(),
132+
&offset_range_max.to_targets(),
133+
&accumulator.to_targets(),
134+
)
135+
.register(b);
136+
137+
RecordWires {
138+
indexed_items,
139+
index_ids,
140+
tree_hash,
141+
counter,
142+
is_stored_in_leaf,
143+
offset_range_min,
144+
offset_range_max,
145+
}
146+
}
147+
148+
fn assign(&self, pw: &mut PartialWitness<F>, wires: &RecordWires<MAX_NUM_RESULTS>) {
149+
wires
150+
.indexed_items
151+
.iter()
152+
.zip(self.indexed_items)
153+
.for_each(|(t, v)| pw.set_u256_target(t, v));
154+
pw.set_target_arr(&wires.index_ids, &self.index_ids);
155+
pw.set_hash_target(wires.tree_hash, self.tree_hash);
156+
pw.set_target(wires.counter, self.counter);
157+
pw.set_bool_target(wires.is_stored_in_leaf, self.is_stored_in_leaf);
158+
pw.set_u256_target(&wires.offset_range_min, self.offset_range_min);
159+
pw.set_u256_target(&wires.offset_range_max, self.offset_range_max);
160+
}
161+
}
162+
163+
/// Verified proof number = 0
164+
pub(crate) const NUM_VERIFIED_PROOFS: usize = 0;
165+
166+
impl<const MAX_NUM_RESULTS: usize> CircuitLogicWires<F, D, NUM_VERIFIED_PROOFS>
167+
for RecordWires<MAX_NUM_RESULTS>
168+
{
169+
type CircuitBuilderParams = ();
170+
type Inputs = RecordCircuit<MAX_NUM_RESULTS>;
171+
172+
const NUM_PUBLIC_INPUTS: usize = PublicInputs::<F, MAX_NUM_RESULTS>::total_len();
173+
174+
fn circuit_logic(
175+
builder: &mut CBuilder,
176+
_verified_proofs: [&ProofWithPublicInputsTarget<D>; NUM_VERIFIED_PROOFS],
177+
_builder_parameters: Self::CircuitBuilderParams,
178+
) -> Self {
179+
Self::Inputs::build(builder)
180+
}
181+
182+
fn assign_input(&self, inputs: Self::Inputs, pw: &mut PartialWitness<F>) -> anyhow::Result<()> {
183+
inputs.assign(pw, self);
184+
Ok(())
185+
}
186+
}
187+
188+
#[cfg(test)]
189+
mod tests {
190+
use std::array;
191+
192+
use super::*;
193+
use mp2_common::{
194+
utils::{FromFields, ToFields},
195+
C,
196+
};
197+
use mp2_test::{
198+
circuit::{run_circuit, UserCircuit},
199+
utils::{gen_random_field_hash, gen_random_u256},
200+
};
201+
use plonky2::{field::types::Field, plonk::config::Hasher};
202+
use rand::{thread_rng, Rng};
203+
204+
const MAX_NUM_RESULTS: usize = 20;
205+
206+
impl UserCircuit<F, D> for RecordCircuit<MAX_NUM_RESULTS> {
207+
type Wires = RecordWires<MAX_NUM_RESULTS>;
208+
209+
fn build(b: &mut CBuilder) -> Self::Wires {
210+
RecordCircuit::build(b)
211+
}
212+
213+
fn prove(&self, pw: &mut PartialWitness<F>, wires: &Self::Wires) {
214+
self.assign(pw, wires);
215+
}
216+
}
217+
218+
fn test_record_circuit(is_stored_in_leaf: bool) {
219+
// Construct the witness.
220+
let mut rng = thread_rng();
221+
let indexed_items = array::from_fn(|_| gen_random_u256(&mut rng));
222+
let index_ids = array::from_fn(|_| F::from_canonical_usize(rng.gen()));
223+
let tree_hash = gen_random_field_hash();
224+
let counter_value = rng.gen::<u32>();
225+
let counter = F::from_canonical_u32(counter_value);
226+
let offset_range_min_value = counter_value - 1;
227+
let offset_range_max_value = counter_value + 1;
228+
let offset_range_min = U256::from_limbs([offset_range_min_value as u64, 0, 0, 0]);
229+
let offset_range_max = U256::from_limbs([offset_range_max_value as u64, 0, 0, 0]);
230+
// Construct the circuit.
231+
let test_circuit = RecordCircuit {
232+
indexed_items,
233+
index_ids,
234+
tree_hash,
235+
counter,
236+
is_stored_in_leaf,
237+
offset_range_min,
238+
offset_range_max,
239+
};
240+
241+
// Proof for the test circuit.
242+
let proof = run_circuit::<F, D, C, _>(test_circuit);
243+
let pi = PublicInputs::<_, MAX_NUM_RESULTS>::from_slice(&proof.public_inputs);
244+
245+
// Check the public inputs.
246+
247+
// // Tree hash
248+
// if is_stored_in_leaf {
249+
// let empty_hash = empty_poseidon_hash();
250+
// let empty_hash_fields = empty_hash.to_fields();
251+
// let hash_inputs: Vec<_> = empty_hash_fields
252+
// .clone()
253+
// .into_iter()
254+
// .chain(empty_hash_fields)
255+
// .chain(indexed_items[1].to_fields())
256+
// .chain(indexed_items[1].to_fields())
257+
// .chain(iter::once(index_ids[1]))
258+
// .chain(indexed_items[1].to_fields())
259+
// .chain(tree_hash.to_fields())
260+
// .collect();
261+
// let exp_hash = H::hash_no_pad(&hash_inputs);
262+
// assert_eq!(pi.tree_hash(), exp_hash);
263+
// } else {
264+
// assert_eq!(pi.tree_hash(), tree_hash);
265+
// };
266+
267+
// // Min value
268+
// assert_eq!(pi.min_value(), indexed_items[1]);
269+
270+
// // Max value
271+
// assert_eq!(pi.max_value(), indexed_items[1]);
272+
273+
// // Primary index value
274+
// assert_eq!(pi.primary_index_value(), indexed_items[0]);
275+
276+
// // Index ids
277+
// assert_eq!(pi.index_ids(), index_ids);
278+
279+
// // Min counter
280+
// assert_eq!(pi.min_counter(), counter);
281+
282+
// // Max counter
283+
// assert_eq!(pi.max_counter(), counter);
284+
285+
// // Offset range min
286+
// assert_eq!(pi.offset_range_min(), offset_range_min);
287+
288+
// // Offset range max
289+
// assert_eq!(pi.offset_range_max(), offset_range_max);
290+
}
291+
292+
#[test]
293+
fn test_record_circuit_storing_in_leaf() {
294+
test_record_circuit(true);
295+
}
296+
297+
// #[test]
298+
// fn test_record_circuit_storing_in_inter() {
299+
// test_record_circuit(false);
300+
// }
301+
}

0 commit comments

Comments
 (0)