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..0ea2eb331 --- /dev/null +++ b/verifiable-db/src/results_tree/extraction/mod.rs @@ -0,0 +1,4 @@ +pub(crate) mod public_inputs; +pub(crate) mod record; + +use public_inputs::PublicInputs; 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..d32abd107 --- /dev/null +++ b/verifiable-db/src/results_tree/extraction/public_inputs.rs @@ -0,0 +1,411 @@ +//! 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> { + 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> PublicInputs<'a, T> { + 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> PublicInputCommon for PublicInputs<'a, Target> { + 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> PublicInputs<'a, Target> { + 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> PublicInputs<'a, F> { + 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, + }; + + #[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/extraction/record.rs b/verifiable-db/src/results_tree/extraction/record.rs new file mode 100644 index 000000000..7b32da0b0 --- /dev/null +++ b/verifiable-db/src/results_tree/extraction/record.rs @@ -0,0 +1,344 @@ +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; 2], + #[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; 2], + /// 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 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()); + + 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(); + 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(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::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::{group_hashing::map_to_curve_point, 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; + + 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, is_second_index_item_dummy: bool) { + // Construct the witness. + let mut rng = thread_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()); + let offset_range_min = counter - F::ONE; + let offset_range_max = counter + F::ONE; + + // Construct the circuit. + 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, + ); + + 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(); + let empty_hash_fields = empty_hash.to_fields(); + let hash_inputs: Vec<_> = empty_hash_fields + .clone() + .into_iter() + .chain(empty_hash_fields) + .chain(second_indexed_item.to_fields()) + .chain(second_indexed_item.to_fields()) + .chain(iter::once(index_ids[1])) + .chain(second_indexed_item.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(), second_indexed_item); + + // Max value + assert_eq!(pi.max_value(), second_indexed_item); + + // Primary index value + assert_eq!(pi.primary_index_value(), first_indexed_item); + + // 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); + + // Accumulator + { + 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.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] + fn test_record_circuit_storing_in_leaf() { + test_record_circuit(true, false); + } + + #[test] + fn test_record_circuit_storing_in_inter() { + 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); + } +} diff --git a/verifiable-db/src/results_tree/mod.rs b/verifiable-db/src/results_tree/mod.rs index 53396f41a..7bd275a83 100644 --- a/verifiable-db/src/results_tree/mod.rs +++ b/verifiable-db/src/results_tree/mod.rs @@ -1,2 +1,3 @@ pub(crate) mod binding; pub(crate) mod construction; +pub(crate) mod extraction;