From c661e48d70ec23cd4fae550f788a8832e36e1680 Mon Sep 17 00:00:00 2001 From: Insun35 Date: Mon, 5 Aug 2024 16:24:49 +0900 Subject: [PATCH 1/7] feat: add public inputs for results extraction circuits --- .../src/results_tree/extraction/mod.rs | 1 + .../results_tree/extraction/public_inputs.rs | 413 ++++++++++++++++++ verifiable-db/src/results_tree/mod.rs | 1 + 3 files changed, 415 insertions(+) create mode 100644 verifiable-db/src/results_tree/extraction/mod.rs create mode 100644 verifiable-db/src/results_tree/extraction/public_inputs.rs diff --git a/verifiable-db/src/results_tree/extraction/mod.rs b/verifiable-db/src/results_tree/extraction/mod.rs new file mode 100644 index 000000000..f351f191b --- /dev/null +++ b/verifiable-db/src/results_tree/extraction/mod.rs @@ -0,0 +1 @@ +pub(crate) mod public_inputs; diff --git a/verifiable-db/src/results_tree/extraction/public_inputs.rs b/verifiable-db/src/results_tree/extraction/public_inputs.rs new file mode 100644 index 000000000..852dd0794 --- /dev/null +++ b/verifiable-db/src/results_tree/extraction/public_inputs.rs @@ -0,0 +1,413 @@ +//! The public inputs of a set of circuits to extract the actual results +//! to be returned from the results tree + +use alloy::primitives::U256; +use itertools::Itertools; +use mp2_common::{ + public_inputs::{PublicInputCommon, PublicInputRange}, + types::{CBuilder, CURVE_TARGET_LEN}, + u256::{UInt256Target, NUM_LIMBS}, + utils::{FromFields, FromTargets}, + F, +}; +use plonky2::{ + hash::hash_types::{HashOut, HashOutTarget, NUM_HASH_OUT_ELTS}, + iop::target::Target, +}; +use plonky2_ecgfp5::{curve::curve::WeierstrassPoint, gadgets::curve::CurveTarget}; +use std::iter::once; + +/// Public inputs of the circuits to extract results from the results tree +pub enum ResultsExtractionPublicInputs { + /// `H`: `hash` Hash of the subtree rooted in the current node + TreeHash, + /// `min`: `u256` Minimum value of the indexed items for the subtree rooted + /// in the current node; it will correspond to the secondary indexed item for nodes + /// of the rows trees, and to the primary indexed item for nodes of the index tree + MinValue, + /// `max`: `u256` Maximum value of the indexed item for the subtree rooted in the current node; + /// it will correspond to the secondary indexed item for nodes of the rows trees, + /// and to the primary indexed item for nodes on the index tree + MaxValue, + /// `I`: `u256` Value of the primary indexed item for the rows stored in the subtree + /// of rows tree in the current node + PrimaryIndexValue, + /// `index_ids`: `[2]F` Integer identifiers of the indexed items + IndexIds, + /// `min_counter`: `F` Minimum counter across the records in the + /// subtree rooted in the current node + MinCounter, + /// `max_counter`: `F` Maximum counter across the records in the + /// subtree rooted in the current node + MaxCounter, + /// `offset_range_min`: `F` Lower bound of the range `[offset, limit + offset]` derived from the query + OffsetRangeMin, + /// `offset_range_max`: `F` Upper bound of the range `[offset, limit + offset]` derived from the query + OffsetRangeMax, + /// `D`: `Digest` order-agnostic digested employed to accumulate the result to be returned + Accumulator, +} + +#[derive(Clone, Debug)] +pub struct PublicInputs<'a, T, const S: usize> { + h: &'a [T], + min_val: &'a [T], + max_val: &'a [T], + pri_idx_val: &'a [T], + idx_ids: &'a [T], + min_cnt: &'a T, + max_cnt: &'a T, + offset_range_min: &'a T, + offset_range_max: &'a T, + acc: &'a [T], +} + +const NUM_PUBLIC_INPUTS: usize = ResultsExtractionPublicInputs::Accumulator as usize + 1; + +impl<'a, T: Clone, const S: usize> PublicInputs<'a, T, S> { + const PI_RANGES: [PublicInputRange; NUM_PUBLIC_INPUTS] = [ + Self::to_range(ResultsExtractionPublicInputs::TreeHash), + Self::to_range(ResultsExtractionPublicInputs::MinValue), + Self::to_range(ResultsExtractionPublicInputs::MaxValue), + Self::to_range(ResultsExtractionPublicInputs::PrimaryIndexValue), + Self::to_range(ResultsExtractionPublicInputs::IndexIds), + Self::to_range(ResultsExtractionPublicInputs::MinCounter), + Self::to_range(ResultsExtractionPublicInputs::MaxCounter), + Self::to_range(ResultsExtractionPublicInputs::OffsetRangeMin), + Self::to_range(ResultsExtractionPublicInputs::OffsetRangeMax), + Self::to_range(ResultsExtractionPublicInputs::Accumulator), + ]; + + const SIZES: [usize; NUM_PUBLIC_INPUTS] = [ + // Tree hash + NUM_HASH_OUT_ELTS, + // Minimum value + NUM_LIMBS, + // Maximum value + NUM_LIMBS, + // Primary index value + NUM_LIMBS, + // Indexed column IDs + 2, + // Minimum counter + 1, + // Maximum counter + 1, + // Offset Range Min + 1, + // Offset Range Max + 1, + // accumulator + CURVE_TARGET_LEN, + ]; + + pub(crate) const fn to_range(pi: ResultsExtractionPublicInputs) -> PublicInputRange { + let mut i = 0; + let mut offset = 0; + let pi_pos = pi as usize; + while i < pi_pos { + offset += Self::SIZES[i]; + i += 1; + } + offset..offset + Self::SIZES[pi_pos] + } + + pub(crate) const fn total_len() -> usize { + Self::to_range(ResultsExtractionPublicInputs::Accumulator).end + } + + pub(crate) fn to_tree_hash_raw(&self) -> &[T] { + self.h + } + + pub(crate) fn to_min_value_raw(&self) -> &[T] { + self.min_val + } + + pub(crate) fn to_max_value_raw(&self) -> &[T] { + self.max_val + } + + pub(crate) fn to_primary_index_value_raw(&self) -> &[T] { + self.pri_idx_val + } + + pub(crate) fn to_index_ids_raw(&self) -> &[T] { + self.idx_ids + } + + pub(crate) fn to_min_counter_raw(&self) -> &T { + self.min_cnt + } + + pub(crate) fn to_max_counter_raw(&self) -> &T { + self.max_cnt + } + + pub(crate) fn to_offset_range_min_raw(&self) -> &T { + self.offset_range_min + } + + pub(crate) fn to_offset_range_max_raw(&self) -> &T { + self.offset_range_max + } + + pub(crate) fn to_accumulator_raw(&self) -> &[T] { + self.acc + } + + pub fn from_slice(input: &'a [T]) -> Self { + assert!( + input.len() >= Self::total_len(), + "Input slice too short to build results public inputs, must be at least {} elements", + Self::total_len(), + ); + Self { + h: &input[Self::PI_RANGES[0].clone()], + min_val: &input[Self::PI_RANGES[1].clone()], + max_val: &input[Self::PI_RANGES[2].clone()], + pri_idx_val: &input[Self::PI_RANGES[3].clone()], + idx_ids: &input[Self::PI_RANGES[4].clone()], + min_cnt: &input[Self::PI_RANGES[5].clone()][0], + max_cnt: &input[Self::PI_RANGES[6].clone()][0], + offset_range_min: &input[Self::PI_RANGES[7].clone()][0], + offset_range_max: &input[Self::PI_RANGES[8].clone()][0], + acc: &input[Self::PI_RANGES[9].clone()], + } + } + + pub fn new( + h: &'a [T], + min_val: &'a [T], + max_val: &'a [T], + pri_idx_val: &'a [T], + idx_ids: &'a [T], + min_cnt: &'a [T], + max_cnt: &'a [T], + offset_range_min: &'a [T], + offset_range_max: &'a [T], + acc: &'a [T], + ) -> Self { + Self { + h, + min_val, + max_val, + pri_idx_val, + idx_ids, + min_cnt: &min_cnt[0], + max_cnt: &max_cnt[0], + offset_range_min: &offset_range_min[0], + offset_range_max: &offset_range_max[0], + acc, + } + } + + pub fn to_vec(&self) -> Vec { + self.h + .iter() + .chain(self.min_val.iter()) + .chain(self.max_val.iter()) + .chain(self.pri_idx_val.iter()) + .chain(self.idx_ids.iter()) + .chain(once(self.min_cnt)) + .chain(once(self.max_cnt)) + .chain(once(self.offset_range_min)) + .chain(once(self.offset_range_max)) + .chain(self.acc.iter()) + .cloned() + .collect_vec() + } +} + +impl<'a, const S: usize> PublicInputCommon for PublicInputs<'a, Target, S> { + const RANGES: &'static [PublicInputRange] = &Self::PI_RANGES; + + fn register_args(&self, cb: &mut CBuilder) { + cb.register_public_inputs(self.h); + cb.register_public_inputs(self.min_val); + cb.register_public_inputs(self.max_val); + cb.register_public_inputs(self.pri_idx_val); + cb.register_public_inputs(self.idx_ids); + cb.register_public_input(*self.min_cnt); + cb.register_public_input(*self.max_cnt); + cb.register_public_input(*self.offset_range_min); + cb.register_public_input(*self.offset_range_max); + cb.register_public_inputs(self.acc); + } +} + +impl<'a, const S: usize> PublicInputs<'a, Target, S> { + pub fn tree_hash_target(&self) -> HashOutTarget { + HashOutTarget::try_from(self.to_tree_hash_raw()).unwrap() + } + + pub fn min_value_target(&self) -> UInt256Target { + UInt256Target::from_targets(self.to_min_value_raw()) + } + + pub fn max_value_target(&self) -> UInt256Target { + UInt256Target::from_targets(self.to_max_value_raw()) + } + + pub fn primary_index_value_target(&self) -> UInt256Target { + UInt256Target::from_targets(self.to_primary_index_value_raw()) + } + + pub fn index_ids_target(&self) -> [Target; 2] { + self.to_index_ids_raw().try_into().unwrap() + } + + pub fn min_counter_target(&self) -> Target { + *self.to_min_counter_raw() + } + + pub fn max_counter_target(&self) -> Target { + *self.to_max_counter_raw() + } + + pub fn offset_range_min_target(&self) -> Target { + *self.to_offset_range_min_raw() + } + + pub fn offset_range_max_target(&self) -> Target { + *self.to_offset_range_max_raw() + } + + pub fn accumulator_target(&self) -> CurveTarget { + CurveTarget::from_targets(self.to_accumulator_raw()) + } +} + +impl<'a, const S: usize> PublicInputs<'a, F, S> { + pub fn tree_hash(&self) -> HashOut { + HashOut::try_from(self.to_tree_hash_raw()).unwrap() + } + + pub fn min_value(&self) -> U256 { + U256::from_fields(self.to_min_value_raw()) + } + + pub fn max_value(&self) -> U256 { + U256::from_fields(self.to_max_value_raw()) + } + + pub fn primary_index_value(&self) -> U256 { + U256::from_fields(self.to_primary_index_value_raw()) + } + + pub fn index_ids(&self) -> [F; 2] { + self.to_index_ids_raw().try_into().unwrap() + } + + pub fn min_counter(&self) -> F { + *self.to_min_counter_raw() + } + + pub fn max_counter(&self) -> F { + *self.to_max_counter_raw() + } + + pub fn offset_range_min(&self) -> F { + *self.to_offset_range_min_raw() + } + + pub fn offset_range_max(&self) -> F { + *self.to_offset_range_max_raw() + } + + pub fn accumulator(&self) -> WeierstrassPoint { + WeierstrassPoint::from_fields(self.to_accumulator_raw()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use mp2_common::{public_inputs::PublicInputCommon, utils::ToFields, C, D, F}; + use mp2_test::{ + circuit::{run_circuit, UserCircuit}, + utils::random_vector, + }; + use plonky2::{ + iop::{ + target::Target, + witness::{PartialWitness, WitnessWrite}, + }, + plonk::circuit_builder::CircuitBuilder, + }; + + const S: usize = 10; + #[derive(Clone, Debug)] + struct TestPublicInputs<'a> { + pis: &'a [F], + } + + impl<'a> UserCircuit for TestPublicInputs<'a> { + type Wires = Vec; + + fn build(c: &mut CircuitBuilder) -> Self::Wires { + let targets = c.add_virtual_target_arr::<{ PublicInputs::::total_len() }>(); + let pi_targets = PublicInputs::::from_slice(targets.as_slice()); + pi_targets.register_args(c); + pi_targets.to_vec() + } + + fn prove(&self, pw: &mut PartialWitness, wires: &Self::Wires) { + pw.set_target_arr(wires, self.pis) + } + } + + #[test] + fn test_results_extraction_public_inputs() { + let pis_raw = random_vector::(PublicInputs::::total_len()).to_fields(); + + // use public inputs in circuit + let test_circuit = TestPublicInputs { pis: &pis_raw }; + let proof = run_circuit::(test_circuit); + assert_eq!(proof.public_inputs, pis_raw); + + // check public inputs are constructed correctly + let pis = PublicInputs::::from_slice(&proof.public_inputs); + assert_eq!( + &pis_raw[PublicInputs::::to_range(ResultsExtractionPublicInputs::TreeHash)], + pis.to_tree_hash_raw(), + ); + assert_eq!( + &pis_raw[PublicInputs::::to_range(ResultsExtractionPublicInputs::MinValue)], + pis.to_min_value_raw(), + ); + assert_eq!( + &pis_raw[PublicInputs::::to_range(ResultsExtractionPublicInputs::MaxValue)], + pis.to_max_value_raw(), + ); + assert_eq!( + &pis_raw + [PublicInputs::::to_range(ResultsExtractionPublicInputs::PrimaryIndexValue)], + pis.to_primary_index_value_raw(), + ); + assert_eq!( + &pis_raw[PublicInputs::::to_range(ResultsExtractionPublicInputs::IndexIds)], + pis.to_index_ids_raw(), + ); + assert_eq!( + &pis_raw[PublicInputs::::to_range(ResultsExtractionPublicInputs::MinCounter)], + &[*pis.to_min_counter_raw()], + ); + assert_eq!( + &pis_raw[PublicInputs::::to_range(ResultsExtractionPublicInputs::MaxCounter)], + &[*pis.to_max_counter_raw()], + ); + assert_eq!( + &pis_raw[PublicInputs::::to_range(ResultsExtractionPublicInputs::OffsetRangeMin)], + &[*pis.to_offset_range_min_raw()], + ); + assert_eq!( + &pis_raw[PublicInputs::::to_range(ResultsExtractionPublicInputs::OffsetRangeMax)], + &[*pis.to_offset_range_max_raw()], + ); + assert_eq!( + &pis_raw[PublicInputs::::to_range(ResultsExtractionPublicInputs::Accumulator)], + pis.to_accumulator_raw(), + ); + } +} diff --git a/verifiable-db/src/results_tree/mod.rs b/verifiable-db/src/results_tree/mod.rs index 8fc0132fc..33ff4180a 100644 --- a/verifiable-db/src/results_tree/mod.rs +++ b/verifiable-db/src/results_tree/mod.rs @@ -1 +1,2 @@ pub(crate) mod construction; +pub(crate) mod extraction; From 1ab533717a5ac7981c070e315b21391be749fa42 Mon Sep 17 00:00:00 2001 From: Insun35 Date: Tue, 6 Aug 2024 16:56:52 +0900 Subject: [PATCH 2/7] feat: implement record circuit --- .../src/results_tree/extraction/mod.rs | 3 + .../src/results_tree/extraction/record.rs | 293 ++++++++++++++++++ 2 files changed, 296 insertions(+) create mode 100644 verifiable-db/src/results_tree/extraction/record.rs diff --git a/verifiable-db/src/results_tree/extraction/mod.rs b/verifiable-db/src/results_tree/extraction/mod.rs index f351f191b..0ea2eb331 100644 --- a/verifiable-db/src/results_tree/extraction/mod.rs +++ b/verifiable-db/src/results_tree/extraction/mod.rs @@ -1 +1,4 @@ pub(crate) mod public_inputs; +pub(crate) mod record; + +use public_inputs::PublicInputs; diff --git a/verifiable-db/src/results_tree/extraction/record.rs b/verifiable-db/src/results_tree/extraction/record.rs new file mode 100644 index 000000000..d850b213d --- /dev/null +++ b/verifiable-db/src/results_tree/extraction/record.rs @@ -0,0 +1,293 @@ +use crate::results_tree::extraction::PublicInputs; +use alloy::primitives::U256; +use mp2_common::{ + group_hashing::CircuitBuilderGroupHashing, + poseidon::{empty_poseidon_hash, H}, + public_inputs::PublicInputCommon, + serialization::{deserialize, deserialize_long_array, serialize, serialize_long_array}, + types::CBuilder, + u256::{CircuitBuilderU256, UInt256Target, WitnessWriteU256}, + utils::{greater_than, less_than, SelectHashBuilder, ToTargets}, + D, F, +}; +use plonky2::{ + hash::hash_types::{HashOut, HashOutTarget}, + iop::{ + target::{BoolTarget, Target}, + witness::{PartialWitness, WitnessWrite}, + }, + plonk::proof::ProofWithPublicInputsTarget, +}; +use recursion_framework::circuit_builder::CircuitLogicWires; +use serde::{Deserialize, Serialize}; +use std::iter; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct RecordWires { + #[serde( + serialize_with = "serialize_long_array", + deserialize_with = "deserialize_long_array" + )] + indexed_items: [UInt256Target; MAX_NUM_RESULTS], + #[serde( + serialize_with = "serialize_long_array", + deserialize_with = "deserialize_long_array" + )] + index_ids: [Target; 2], + #[serde(serialize_with = "serialize", deserialize_with = "deserialize")] + tree_hash: HashOutTarget, + counter: Target, + #[serde(serialize_with = "serialize", deserialize_with = "deserialize")] + is_stored_in_leaf: BoolTarget, + offset_range_min: Target, + offset_range_max: Target, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct RecordCircuit { + /// Values of the indexed items for in this record; + /// if there is no secondary indexed item, just place the dummy value `0` + #[serde( + serialize_with = "serialize_long_array", + deserialize_with = "deserialize_long_array" + )] + pub(crate) indexed_items: [U256; MAX_NUM_RESULTS], + /// Integer identifiers of the indexed items + #[serde( + serialize_with = "serialize_long_array", + deserialize_with = "deserialize_long_array" + )] + pub(crate) index_ids: [F; 2], + /// Hash of the cells tree for this record; + /// could be empty hash if there is no cells tree for this record + pub(crate) tree_hash: HashOut, + /// Counter for the node associated to this record + pub(crate) counter: F, + /// Boolean flag specifying whether this record is stored + /// in a leaf node of a rows tree or not + pub(crate) is_stored_in_leaf: bool, + /// Minimum offset range bound + pub(crate) offset_range_min: F, + /// Maximum offset range bound + pub(crate) offset_range_max: F, +} + +impl RecordCircuit { + pub fn build(b: &mut CBuilder) -> RecordWires { + let ffalse = b._false(); + let empty_hash = b.constant_hash(*empty_poseidon_hash()); + + let indexed_items: [UInt256Target; MAX_NUM_RESULTS] = b.add_virtual_u256_arr_unsafe(); + let index_ids: [Target; 2] = b.add_virtual_target_arr(); + let tree_hash = b.add_virtual_hash(); + let counter = b.add_virtual_target(); + let is_stored_in_leaf = b.add_virtual_bool_target_safe(); + let [offset_range_min, offset_range_max] = b.add_virtual_target_arr(); + + // H(H("")||H("")||indexed_items[1]||indexed_items[1]||index_ids[1]||indexed_items[1]||tree_hash) + let tree_hash_inputs = empty_hash + .elements + .iter() + .cloned() + .chain(empty_hash.elements) + .chain(indexed_items[1].to_targets()) + .chain(indexed_items[1].to_targets()) + .chain(iter::once(index_ids[1])) + .chain(indexed_items[1].to_targets()) + .chain(tree_hash.elements) + .collect(); + let new_tree_hash = b.hash_n_to_hash_no_pad::(tree_hash_inputs); + let final_tree_hash = b.select_hash(is_stored_in_leaf, &new_tree_hash, &tree_hash); + + // D(index_ids[0]||indexed_items[0]||index_ids[1]||indexed_items[1]||tree_hash) + let accumulator_inputs: Vec<_> = iter::once(index_ids[0]) + .chain(indexed_items[0].to_targets()) + .chain(iter::once(index_ids[1])) + .chain(indexed_items[1].to_targets()) + .chain(final_tree_hash.to_targets()) + .collect(); + let accumulator = b.map_to_curve_point(&accumulator_inputs); + + // Ensure the counter associated to the current record is in the range + // specified by the query + // offset_range_min <= counter <= offset_range_max + // -> NOT((counter < offset_range_min) OR (counter > offset_range_max) + let is_less_than = less_than(b, counter, offset_range_min, 32); + let is_greater_than = greater_than(b, counter, offset_range_max, 32); + let is_out_of_range = b.or(is_less_than, is_greater_than); + b.connect(is_out_of_range.target, ffalse.target); + + // Register the public inputs. + PublicInputs::<_, MAX_NUM_RESULTS>::new( + &final_tree_hash.to_targets(), + &indexed_items[1].to_targets(), + &indexed_items[1].to_targets(), + &indexed_items[0].to_targets(), + &index_ids, + &[counter], + &[counter], + &[offset_range_min], + &[offset_range_max], + &accumulator.to_targets(), + ) + .register(b); + + RecordWires { + indexed_items, + index_ids, + tree_hash, + counter, + is_stored_in_leaf, + offset_range_min, + offset_range_max, + } + } + + fn assign(&self, pw: &mut PartialWitness, wires: &RecordWires) { + wires + .indexed_items + .iter() + .zip(self.indexed_items) + .for_each(|(t, v)| pw.set_u256_target(t, v)); + pw.set_target_arr(&wires.index_ids, &self.index_ids); + pw.set_hash_target(wires.tree_hash, self.tree_hash); + pw.set_target(wires.counter, self.counter); + pw.set_bool_target(wires.is_stored_in_leaf, self.is_stored_in_leaf); + pw.set_target(wires.offset_range_min, self.offset_range_min); + pw.set_target(wires.offset_range_max, self.offset_range_max); + } +} + +/// Verified proof number = 0 +pub(crate) const NUM_VERIFIED_PROOFS: usize = 0; + +impl CircuitLogicWires + for RecordWires +{ + type CircuitBuilderParams = (); + type Inputs = RecordCircuit; + + const NUM_PUBLIC_INPUTS: usize = PublicInputs::::total_len(); + + fn circuit_logic( + builder: &mut CBuilder, + _verified_proofs: [&ProofWithPublicInputsTarget; NUM_VERIFIED_PROOFS], + _builder_parameters: Self::CircuitBuilderParams, + ) -> Self { + Self::Inputs::build(builder) + } + + fn assign_input(&self, inputs: Self::Inputs, pw: &mut PartialWitness) -> anyhow::Result<()> { + inputs.assign(pw, self); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use mp2_common::{utils::ToFields, C}; + use mp2_test::{ + circuit::{run_circuit, UserCircuit}, + utils::{gen_random_field_hash, gen_random_u256}, + }; + use plonky2::{field::types::Field, plonk::config::Hasher}; + use rand::{thread_rng, Rng}; + use std::array; + + const MAX_NUM_RESULTS: usize = 20; + + impl UserCircuit for RecordCircuit { + type Wires = RecordWires; + + fn build(b: &mut CBuilder) -> Self::Wires { + RecordCircuit::build(b) + } + + fn prove(&self, pw: &mut PartialWitness, wires: &Self::Wires) { + self.assign(pw, wires); + } + } + + fn test_record_circuit(is_stored_in_leaf: bool) { + // Construct the witness. + let mut rng = thread_rng(); + let indexed_items = array::from_fn(|_| gen_random_u256(&mut rng)); + let index_ids = array::from_fn(|_| F::from_canonical_usize(rng.gen())); + let tree_hash = gen_random_field_hash(); + let counter = F::from_canonical_u32(rng.gen()); + let offset_range_min = counter - F::ONE; + let offset_range_max = counter + F::ONE; + + // Construct the circuit. + let test_circuit = RecordCircuit { + indexed_items, + index_ids, + tree_hash, + counter, + is_stored_in_leaf, + offset_range_min, + offset_range_max, + }; + + // Proof for the test circuit. + let proof = run_circuit::(test_circuit); + let pi = PublicInputs::<_, MAX_NUM_RESULTS>::from_slice(&proof.public_inputs); + + // Check the public inputs. + + // Tree hash + if is_stored_in_leaf { + let empty_hash = empty_poseidon_hash(); + let empty_hash_fields = empty_hash.to_fields(); + let hash_inputs: Vec<_> = empty_hash_fields + .clone() + .into_iter() + .chain(empty_hash_fields) + .chain(indexed_items[1].to_fields()) + .chain(indexed_items[1].to_fields()) + .chain(iter::once(index_ids[1])) + .chain(indexed_items[1].to_fields()) + .chain(tree_hash.to_fields()) + .collect(); + let exp_hash = H::hash_no_pad(&hash_inputs); + assert_eq!(pi.tree_hash(), exp_hash); + } else { + assert_eq!(pi.tree_hash(), tree_hash); + }; + + // Min value + assert_eq!(pi.min_value(), indexed_items[1]); + + // Max value + assert_eq!(pi.max_value(), indexed_items[1]); + + // Primary index value + assert_eq!(pi.primary_index_value(), indexed_items[0]); + + // Index ids + assert_eq!(pi.index_ids(), index_ids); + + // Min counter + assert_eq!(pi.min_counter(), counter); + + // Max counter + assert_eq!(pi.max_counter(), counter); + + // Offset range min + assert_eq!(pi.offset_range_min(), offset_range_min); + + // Offset range max + assert_eq!(pi.offset_range_max(), offset_range_max); + } + + #[test] + fn test_record_circuit_storing_in_leaf() { + test_record_circuit(true); + } + + #[test] + fn test_record_circuit_storing_in_inter() { + test_record_circuit(false); + } +} From 5fbe4ea69b6733114439f3fbd42fb977dd746027 Mon Sep 17 00:00:00 2001 From: Insun35 Date: Tue, 13 Aug 2024 15:57:15 +0900 Subject: [PATCH 3/7] refac: remove redundant const generic --- .../results_tree/extraction/public_inputs.rs | 39 +++++++++---------- .../src/results_tree/extraction/record.rs | 34 +++++++--------- 2 files changed, 34 insertions(+), 39 deletions(-) diff --git a/verifiable-db/src/results_tree/extraction/public_inputs.rs b/verifiable-db/src/results_tree/extraction/public_inputs.rs index 852dd0794..c8a468ff1 100644 --- a/verifiable-db/src/results_tree/extraction/public_inputs.rs +++ b/verifiable-db/src/results_tree/extraction/public_inputs.rs @@ -49,7 +49,7 @@ pub enum ResultsExtractionPublicInputs { } #[derive(Clone, Debug)] -pub struct PublicInputs<'a, T, const S: usize> { +pub struct PublicInputs<'a, T> { h: &'a [T], min_val: &'a [T], max_val: &'a [T], @@ -64,7 +64,7 @@ pub struct PublicInputs<'a, T, const S: usize> { const NUM_PUBLIC_INPUTS: usize = ResultsExtractionPublicInputs::Accumulator as usize + 1; -impl<'a, T: Clone, const S: usize> PublicInputs<'a, T, S> { +impl<'a, T: Clone> PublicInputs<'a, T> { const PI_RANGES: [PublicInputRange; NUM_PUBLIC_INPUTS] = [ Self::to_range(ResultsExtractionPublicInputs::TreeHash), Self::to_range(ResultsExtractionPublicInputs::MinValue), @@ -219,7 +219,7 @@ impl<'a, T: Clone, const S: usize> PublicInputs<'a, T, S> { } } -impl<'a, const S: usize> PublicInputCommon for PublicInputs<'a, Target, S> { +impl<'a> PublicInputCommon for PublicInputs<'a, Target> { const RANGES: &'static [PublicInputRange] = &Self::PI_RANGES; fn register_args(&self, cb: &mut CBuilder) { @@ -236,7 +236,7 @@ impl<'a, const S: usize> PublicInputCommon for PublicInputs<'a, Target, S> { } } -impl<'a, const S: usize> PublicInputs<'a, Target, S> { +impl<'a> PublicInputs<'a, Target> { pub fn tree_hash_target(&self) -> HashOutTarget { HashOutTarget::try_from(self.to_tree_hash_raw()).unwrap() } @@ -278,7 +278,7 @@ impl<'a, const S: usize> PublicInputs<'a, Target, S> { } } -impl<'a, const S: usize> PublicInputs<'a, F, S> { +impl<'a> PublicInputs<'a, F> { pub fn tree_hash(&self) -> HashOut { HashOut::try_from(self.to_tree_hash_raw()).unwrap() } @@ -346,8 +346,8 @@ mod tests { type Wires = Vec; fn build(c: &mut CircuitBuilder) -> Self::Wires { - let targets = c.add_virtual_target_arr::<{ PublicInputs::::total_len() }>(); - let pi_targets = PublicInputs::::from_slice(targets.as_slice()); + let targets = c.add_virtual_target_arr::<{ PublicInputs::::total_len() }>(); + let pi_targets = PublicInputs::::from_slice(targets.as_slice()); pi_targets.register_args(c); pi_targets.to_vec() } @@ -359,7 +359,7 @@ mod tests { #[test] fn test_results_extraction_public_inputs() { - let pis_raw = random_vector::(PublicInputs::::total_len()).to_fields(); + let pis_raw = random_vector::(PublicInputs::::total_len()).to_fields(); // use public inputs in circuit let test_circuit = TestPublicInputs { pis: &pis_raw }; @@ -367,46 +367,45 @@ mod tests { assert_eq!(proof.public_inputs, pis_raw); // check public inputs are constructed correctly - let pis = PublicInputs::::from_slice(&proof.public_inputs); + let pis = PublicInputs::::from_slice(&proof.public_inputs); assert_eq!( - &pis_raw[PublicInputs::::to_range(ResultsExtractionPublicInputs::TreeHash)], + &pis_raw[PublicInputs::::to_range(ResultsExtractionPublicInputs::TreeHash)], pis.to_tree_hash_raw(), ); assert_eq!( - &pis_raw[PublicInputs::::to_range(ResultsExtractionPublicInputs::MinValue)], + &pis_raw[PublicInputs::::to_range(ResultsExtractionPublicInputs::MinValue)], pis.to_min_value_raw(), ); assert_eq!( - &pis_raw[PublicInputs::::to_range(ResultsExtractionPublicInputs::MaxValue)], + &pis_raw[PublicInputs::::to_range(ResultsExtractionPublicInputs::MaxValue)], pis.to_max_value_raw(), ); assert_eq!( - &pis_raw - [PublicInputs::::to_range(ResultsExtractionPublicInputs::PrimaryIndexValue)], + &pis_raw[PublicInputs::::to_range(ResultsExtractionPublicInputs::PrimaryIndexValue)], pis.to_primary_index_value_raw(), ); assert_eq!( - &pis_raw[PublicInputs::::to_range(ResultsExtractionPublicInputs::IndexIds)], + &pis_raw[PublicInputs::::to_range(ResultsExtractionPublicInputs::IndexIds)], pis.to_index_ids_raw(), ); assert_eq!( - &pis_raw[PublicInputs::::to_range(ResultsExtractionPublicInputs::MinCounter)], + &pis_raw[PublicInputs::::to_range(ResultsExtractionPublicInputs::MinCounter)], &[*pis.to_min_counter_raw()], ); assert_eq!( - &pis_raw[PublicInputs::::to_range(ResultsExtractionPublicInputs::MaxCounter)], + &pis_raw[PublicInputs::::to_range(ResultsExtractionPublicInputs::MaxCounter)], &[*pis.to_max_counter_raw()], ); assert_eq!( - &pis_raw[PublicInputs::::to_range(ResultsExtractionPublicInputs::OffsetRangeMin)], + &pis_raw[PublicInputs::::to_range(ResultsExtractionPublicInputs::OffsetRangeMin)], &[*pis.to_offset_range_min_raw()], ); assert_eq!( - &pis_raw[PublicInputs::::to_range(ResultsExtractionPublicInputs::OffsetRangeMax)], + &pis_raw[PublicInputs::::to_range(ResultsExtractionPublicInputs::OffsetRangeMax)], &[*pis.to_offset_range_max_raw()], ); assert_eq!( - &pis_raw[PublicInputs::::to_range(ResultsExtractionPublicInputs::Accumulator)], + &pis_raw[PublicInputs::::to_range(ResultsExtractionPublicInputs::Accumulator)], pis.to_accumulator_raw(), ); } diff --git a/verifiable-db/src/results_tree/extraction/record.rs b/verifiable-db/src/results_tree/extraction/record.rs index d850b213d..45af6e220 100644 --- a/verifiable-db/src/results_tree/extraction/record.rs +++ b/verifiable-db/src/results_tree/extraction/record.rs @@ -23,12 +23,12 @@ use serde::{Deserialize, Serialize}; use std::iter; #[derive(Clone, Debug, Serialize, Deserialize)] -pub struct RecordWires { +pub struct RecordWires { #[serde( serialize_with = "serialize_long_array", deserialize_with = "deserialize_long_array" )] - indexed_items: [UInt256Target; MAX_NUM_RESULTS], + indexed_items: [UInt256Target; 2], #[serde( serialize_with = "serialize_long_array", deserialize_with = "deserialize_long_array" @@ -44,14 +44,14 @@ pub struct RecordWires { } #[derive(Clone, Debug, Serialize, Deserialize)] -pub struct RecordCircuit { +pub struct RecordCircuit { /// Values of the indexed items for in this record; /// if there is no secondary indexed item, just place the dummy value `0` #[serde( serialize_with = "serialize_long_array", deserialize_with = "deserialize_long_array" )] - pub(crate) indexed_items: [U256; MAX_NUM_RESULTS], + pub(crate) indexed_items: [U256; 2], /// Integer identifiers of the indexed items #[serde( serialize_with = "serialize_long_array", @@ -72,12 +72,12 @@ pub struct RecordCircuit { pub(crate) offset_range_max: F, } -impl RecordCircuit { - pub fn build(b: &mut CBuilder) -> RecordWires { +impl RecordCircuit { + pub fn build(b: &mut CBuilder) -> RecordWires { let ffalse = b._false(); let empty_hash = b.constant_hash(*empty_poseidon_hash()); - let indexed_items: [UInt256Target; MAX_NUM_RESULTS] = b.add_virtual_u256_arr_unsafe(); + let indexed_items: [UInt256Target; 2] = b.add_virtual_u256_arr_unsafe(); let index_ids: [Target; 2] = b.add_virtual_target_arr(); let tree_hash = b.add_virtual_hash(); let counter = b.add_virtual_target(); @@ -118,7 +118,7 @@ impl RecordCircuit { b.connect(is_out_of_range.target, ffalse.target); // Register the public inputs. - PublicInputs::<_, MAX_NUM_RESULTS>::new( + PublicInputs::new( &final_tree_hash.to_targets(), &indexed_items[1].to_targets(), &indexed_items[1].to_targets(), @@ -143,7 +143,7 @@ impl RecordCircuit { } } - fn assign(&self, pw: &mut PartialWitness, wires: &RecordWires) { + fn assign(&self, pw: &mut PartialWitness, wires: &RecordWires) { wires .indexed_items .iter() @@ -161,13 +161,11 @@ impl RecordCircuit { /// Verified proof number = 0 pub(crate) const NUM_VERIFIED_PROOFS: usize = 0; -impl CircuitLogicWires - for RecordWires -{ +impl CircuitLogicWires for RecordWires { type CircuitBuilderParams = (); - type Inputs = RecordCircuit; + type Inputs = RecordCircuit; - const NUM_PUBLIC_INPUTS: usize = PublicInputs::::total_len(); + const NUM_PUBLIC_INPUTS: usize = PublicInputs::::total_len(); fn circuit_logic( builder: &mut CBuilder, @@ -195,10 +193,8 @@ mod tests { use rand::{thread_rng, Rng}; use std::array; - const MAX_NUM_RESULTS: usize = 20; - - impl UserCircuit for RecordCircuit { - type Wires = RecordWires; + impl UserCircuit for RecordCircuit { + type Wires = RecordWires; fn build(b: &mut CBuilder) -> Self::Wires { RecordCircuit::build(b) @@ -232,7 +228,7 @@ mod tests { // Proof for the test circuit. let proof = run_circuit::(test_circuit); - let pi = PublicInputs::<_, MAX_NUM_RESULTS>::from_slice(&proof.public_inputs); + let pi = PublicInputs::from_slice(&proof.public_inputs); // Check the public inputs. From e2e58f84b8720fcc8c2c8d78cc3b26c68f42bf81 Mon Sep 17 00:00:00 2001 From: Insun35 Date: Tue, 13 Aug 2024 16:10:42 +0900 Subject: [PATCH 4/7] fix: replace acc tree hash input - replace accumulator input from final_tree_hash to tree_hash - add test for accumulator check --- .../src/results_tree/extraction/record.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/verifiable-db/src/results_tree/extraction/record.rs b/verifiable-db/src/results_tree/extraction/record.rs index 45af6e220..0ceb2b330 100644 --- a/verifiable-db/src/results_tree/extraction/record.rs +++ b/verifiable-db/src/results_tree/extraction/record.rs @@ -104,7 +104,7 @@ impl RecordCircuit { .chain(indexed_items[0].to_targets()) .chain(iter::once(index_ids[1])) .chain(indexed_items[1].to_targets()) - .chain(final_tree_hash.to_targets()) + .chain(tree_hash.to_targets()) .collect(); let accumulator = b.map_to_curve_point(&accumulator_inputs); @@ -184,7 +184,7 @@ impl CircuitLogicWires for RecordWires { #[cfg(test)] mod tests { use super::*; - use mp2_common::{utils::ToFields, C}; + use mp2_common::{group_hashing::map_to_curve_point, utils::ToFields, C}; use mp2_test::{ circuit::{run_circuit, UserCircuit}, utils::{gen_random_field_hash, gen_random_u256}, @@ -275,6 +275,18 @@ mod tests { // Offset range max assert_eq!(pi.offset_range_max(), offset_range_max); + + // Accumulator + { + let accumulator_inputs: Vec<_> = iter::once(index_ids[0]) + .chain(indexed_items[0].to_fields()) + .chain(iter::once(index_ids[1])) + .chain(indexed_items[1].to_fields()) + .chain(tree_hash.to_fields()) + .collect(); + let exp_accumulator = map_to_curve_point(&accumulator_inputs); + assert_eq!(pi.accumulator(), exp_accumulator.to_weierstrass()); + } } #[test] From df3364fc4444af43f77d73fdb5fb3b34c535896b Mon Sep 17 00:00:00 2001 From: Insun35 Date: Tue, 13 Aug 2024 16:48:20 +0900 Subject: [PATCH 5/7] feat: add new method to initialize circuit with dummy item Add new method to properly initialize the circuit ensuring that indexed_items[1] == U256::ZERO if it is a dummy value. Also modify test code to use new method when constructing the circuit. --- .../src/results_tree/extraction/record.rs | 72 +++++++++++++++---- 1 file changed, 57 insertions(+), 15 deletions(-) diff --git a/verifiable-db/src/results_tree/extraction/record.rs b/verifiable-db/src/results_tree/extraction/record.rs index 0ceb2b330..87e2d73c9 100644 --- a/verifiable-db/src/results_tree/extraction/record.rs +++ b/verifiable-db/src/results_tree/extraction/record.rs @@ -73,6 +73,32 @@ pub struct RecordCircuit { } impl RecordCircuit { + pub fn new( + first_indexed_item: U256, + second_indexed_item: Option, + index_ids: [F; 2], + tree_hash: HashOut, + counter: F, + is_stored_in_leaf: bool, + offset_range_min: F, + offset_range_max: F, + ) -> Self { + let indexed_items = [ + first_indexed_item, + second_indexed_item.unwrap_or(U256::ZERO), + ]; + + Self { + indexed_items, + index_ids, + tree_hash, + counter, + is_stored_in_leaf, + offset_range_min, + offset_range_max, + } + } + pub fn build(b: &mut CBuilder) -> RecordWires { let ffalse = b._false(); let empty_hash = b.constant_hash(*empty_poseidon_hash()); @@ -205,10 +231,15 @@ mod tests { } } - fn test_record_circuit(is_stored_in_leaf: bool) { + fn test_record_circuit(is_stored_in_leaf: bool, is_second_index_item_dummy: bool) { // Construct the witness. let mut rng = thread_rng(); - let indexed_items = array::from_fn(|_| gen_random_u256(&mut rng)); + let first_indexed_item = gen_random_u256(&mut rng); + let second_indexed_item = if is_second_index_item_dummy { + None + } else { + Some(gen_random_u256(&mut rng)) + }; let index_ids = array::from_fn(|_| F::from_canonical_usize(rng.gen())); let tree_hash = gen_random_field_hash(); let counter = F::from_canonical_u32(rng.gen()); @@ -216,15 +247,16 @@ mod tests { let offset_range_max = counter + F::ONE; // Construct the circuit. - let test_circuit = RecordCircuit { - indexed_items, + let test_circuit = RecordCircuit::new( + first_indexed_item, + second_indexed_item, index_ids, tree_hash, counter, is_stored_in_leaf, offset_range_min, offset_range_max, - }; + ); // Proof for the test circuit. let proof = run_circuit::(test_circuit); @@ -240,10 +272,10 @@ mod tests { .clone() .into_iter() .chain(empty_hash_fields) - .chain(indexed_items[1].to_fields()) - .chain(indexed_items[1].to_fields()) + .chain(second_indexed_item.unwrap_or(U256::ZERO).to_fields()) + .chain(second_indexed_item.unwrap_or(U256::ZERO).to_fields()) .chain(iter::once(index_ids[1])) - .chain(indexed_items[1].to_fields()) + .chain(second_indexed_item.unwrap_or(U256::ZERO).to_fields()) .chain(tree_hash.to_fields()) .collect(); let exp_hash = H::hash_no_pad(&hash_inputs); @@ -253,13 +285,13 @@ mod tests { }; // Min value - assert_eq!(pi.min_value(), indexed_items[1]); + assert_eq!(pi.min_value(), second_indexed_item.unwrap_or(U256::ZERO)); // Max value - assert_eq!(pi.max_value(), indexed_items[1]); + assert_eq!(pi.max_value(), second_indexed_item.unwrap_or(U256::ZERO)); // Primary index value - assert_eq!(pi.primary_index_value(), indexed_items[0]); + assert_eq!(pi.primary_index_value(), first_indexed_item); // Index ids assert_eq!(pi.index_ids(), index_ids); @@ -279,9 +311,9 @@ mod tests { // Accumulator { let accumulator_inputs: Vec<_> = iter::once(index_ids[0]) - .chain(indexed_items[0].to_fields()) + .chain(first_indexed_item.to_fields()) .chain(iter::once(index_ids[1])) - .chain(indexed_items[1].to_fields()) + .chain(second_indexed_item.unwrap_or(U256::ZERO).to_fields()) .chain(tree_hash.to_fields()) .collect(); let exp_accumulator = map_to_curve_point(&accumulator_inputs); @@ -291,11 +323,21 @@ mod tests { #[test] fn test_record_circuit_storing_in_leaf() { - test_record_circuit(true); + test_record_circuit(true, false); } #[test] fn test_record_circuit_storing_in_inter() { - test_record_circuit(false); + test_record_circuit(false, false); + } + + #[test] + fn test_record_circuit_storing_in_leaf_with_dummy_item() { + test_record_circuit(true, true); + } + + #[test] + fn test_record_circuit_storing_in_inter_with_dummy_item() { + test_record_circuit(false, true); } } From ade28f76661ca6525a7e10b4970811a8ab845d41 Mon Sep 17 00:00:00 2001 From: Insun35 Date: Wed, 14 Aug 2024 15:49:57 +0900 Subject: [PATCH 6/7] chore: remove unused const --- verifiable-db/src/results_tree/extraction/public_inputs.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/verifiable-db/src/results_tree/extraction/public_inputs.rs b/verifiable-db/src/results_tree/extraction/public_inputs.rs index c8a468ff1..d32abd107 100644 --- a/verifiable-db/src/results_tree/extraction/public_inputs.rs +++ b/verifiable-db/src/results_tree/extraction/public_inputs.rs @@ -336,7 +336,6 @@ mod tests { plonk::circuit_builder::CircuitBuilder, }; - const S: usize = 10; #[derive(Clone, Debug)] struct TestPublicInputs<'a> { pis: &'a [F], From 716e6525856aac93f11dd424252e471bd33e7927 Mon Sep 17 00:00:00 2001 From: Insun35 Date: Wed, 14 Aug 2024 18:59:12 +0900 Subject: [PATCH 7/7] refac: simplify test by using second_indexed_item directly --- .../src/results_tree/extraction/record.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/verifiable-db/src/results_tree/extraction/record.rs b/verifiable-db/src/results_tree/extraction/record.rs index 87e2d73c9..7b32da0b0 100644 --- a/verifiable-db/src/results_tree/extraction/record.rs +++ b/verifiable-db/src/results_tree/extraction/record.rs @@ -258,12 +258,13 @@ mod tests { offset_range_max, ); + let second_indexed_item = test_circuit.indexed_items[1]; + // Proof for the test circuit. let proof = run_circuit::(test_circuit); let pi = PublicInputs::from_slice(&proof.public_inputs); // Check the public inputs. - // Tree hash if is_stored_in_leaf { let empty_hash = empty_poseidon_hash(); @@ -272,10 +273,10 @@ mod tests { .clone() .into_iter() .chain(empty_hash_fields) - .chain(second_indexed_item.unwrap_or(U256::ZERO).to_fields()) - .chain(second_indexed_item.unwrap_or(U256::ZERO).to_fields()) + .chain(second_indexed_item.to_fields()) + .chain(second_indexed_item.to_fields()) .chain(iter::once(index_ids[1])) - .chain(second_indexed_item.unwrap_or(U256::ZERO).to_fields()) + .chain(second_indexed_item.to_fields()) .chain(tree_hash.to_fields()) .collect(); let exp_hash = H::hash_no_pad(&hash_inputs); @@ -285,10 +286,10 @@ mod tests { }; // Min value - assert_eq!(pi.min_value(), second_indexed_item.unwrap_or(U256::ZERO)); + assert_eq!(pi.min_value(), second_indexed_item); // Max value - assert_eq!(pi.max_value(), second_indexed_item.unwrap_or(U256::ZERO)); + assert_eq!(pi.max_value(), second_indexed_item); // Primary index value assert_eq!(pi.primary_index_value(), first_indexed_item); @@ -313,7 +314,7 @@ mod tests { let accumulator_inputs: Vec<_> = iter::once(index_ids[0]) .chain(first_indexed_item.to_fields()) .chain(iter::once(index_ids[1])) - .chain(second_indexed_item.unwrap_or(U256::ZERO).to_fields()) + .chain(second_indexed_item.to_fields()) .chain(tree_hash.to_fields()) .collect(); let exp_accumulator = map_to_curve_point(&accumulator_inputs);