Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions grovedb-element/src/element/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,12 @@ pub enum Element {
ProvableCountProvableSumTree(Option<Vec<u8>>, CountValue, SumValue, Option<ElementFlags>),
/// Provable sum-indexed tree: a `ProvableSumTree`-style primary Merk
/// paired with a single secondary Merk keyed by
/// `(sum_sortable_be ‖ original_key)`. Both Merks contribute to the
/// `(sum_sortable_be ‖ original_key)`. The secondary is a
/// `ProvableCountProvableSumTree` — each row is a `SumItem`
/// contributing `(count = 1, sum)` — so positional queries against
/// the sum ranking are provable via counted subtree commitments:
/// offset pagination in O(log n + k) proof size (k = page size),
/// rank-of-key in O(log n). Both Merks contribute to the
/// element's `combined_value_hash` via the three-input hash composition
/// `combine_hash_three(value_hash, primary_root_hash,
/// secondary_root_hash)`.
Expand Down Expand Up @@ -288,9 +293,11 @@ pub enum Element {
/// `ProvableCountedAndProvableSummedMerkNode` (both count AND sum
/// baked into node hash) and carries a TLV list of 1..=3 secondary
/// Merks — one per selected axis (count, sum, avg). Each secondary
/// lives at its own derived storage prefix and is itself a
/// `ProvableCountProvableSumTree` so any axis can produce both
/// count-on-range and sum-on-range proofs.
/// lives at its own derived storage prefix; the count axis is a
/// `ProvableCountTree` while the sum and avg axes are
/// `ProvableCountProvableSumTree`s, so every axis carries a
/// hash-bound count (enabling count-bound offset pagination) and
/// the sum/avg axes can additionally produce sum-on-range proofs.
///
/// Fields: `(primary_root_key, count_value, sum_value, axes, flags)`
/// - `primary_root_key`: root key of the primary
Expand Down
16 changes: 13 additions & 3 deletions grovedb-element/src/element_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -625,15 +625,25 @@ impl ElementType {
// `KvValueHashFeatureType` — the embedded `TreeFeatureType` carries
// the per-node aggregate(s) so a single proof-node variant suffices
// for the subtree case in every family.
// Indexed-tree primaries dispatch with their own node family:
// PCIT primaries use count-only nodes, PSIT primaries use
// sum-only nodes, and PCPSIT primaries use count-and-sum nodes
// (mirroring `TreeType::inner_node_type`).
let is_provable_count_only_tree = matches!(
parent_base,
Some(ElementType::ProvableCountTree)
| Some(ElementType::ProvableCountSumTree)
| Some(ElementType::ProvableCountIndexedTree)
);
let is_provable_sum_only_tree = matches!(parent_base, Some(ElementType::ProvableSumTree));
let is_provable_count_and_provable_sum_tree =
matches!(parent_base, Some(ElementType::ProvableCountProvableSumTree));
let is_provable_sum_only_tree = matches!(
parent_base,
Some(ElementType::ProvableSumTree) | Some(ElementType::ProvableSumIndexedTree)
);
let is_provable_count_and_provable_sum_tree = matches!(
parent_base,
Some(ElementType::ProvableCountProvableSumTree)
| Some(ElementType::ProvableCountProvableSumIndexedTree)
);
let is_provable_aggregate_tree = is_provable_count_only_tree
|| is_provable_sum_only_tree
|| is_provable_count_and_provable_sum_tree;
Expand Down
3 changes: 2 additions & 1 deletion grovedb/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2166,7 +2166,8 @@ impl GroveDb {
}
}
// ProvableSumIndexedTree integrity: identical shape to
// PCIT but the secondary is a `ProvableSumTree`. Open
// PCIT but the secondary is a
// `ProvableCountProvableSumTree`. Open
// both Merks, recompute `combine_hash_three(value_hash,
// primary_root_hash, secondary_root_hash)`, compare
// to the parent's stored combined value hash, and
Expand Down
23 changes: 15 additions & 8 deletions grovedb/src/operations/indexed_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,12 @@ pub(crate) fn axis_secondary_tree_type(axis: IndexAxis) -> TreeType {
match axis {
// Each count entry contributes count = 1.
IndexAxis::Count => TreeType::ProvableCountTree,
// Each sum entry contributes its own SumValue.
IndexAxis::Sum => TreeType::ProvableSumTree,
// Each sum entry contributes (count = 1, sum = its own SumValue).
// The count half is what makes positional queries against the sum
// ranking provable in O(log n): the count-offset proof primitive
// skips whole subtrees via counted node commitments, which needs
// every secondary node to carry a hash-bound count aggregate.
IndexAxis::Sum => TreeType::ProvableCountProvableSumTree,
// Each avg entry contributes (count = 1, sum = item's SumValue).
IndexAxis::Avg => TreeType::ProvableCountProvableSumTree,
}
Expand Down Expand Up @@ -2198,11 +2202,13 @@ impl GroveDb {
///
/// The secondary entry is a no-payload `Item` whose own sum / count
/// contribution comes from its position in a sum/count-bearing tree.
/// For the sum axis the secondary entry is a `SumItem(sum)`; for the
/// avg axis the secondary entry is an `ItemWithSumItem(empty, sum)` so
/// both count (= 1) and sum (= the entry's sum_value) propagate to the
/// secondary's `ProvableCountProvableSumTree`. For the count axis the
/// secondary entry is a plain `Item` (count = 1, no sum).
/// For the sum axis the secondary entry is a `SumItem(sum)`, which in
/// the secondary's `ProvableCountProvableSumTree` contributes
/// (count = 1, sum); for the avg axis the secondary entry is an
/// `ItemWithSumItem(empty, sum)` so both count (= 1) and sum (= the
/// entry's sum_value) propagate to the secondary's
/// `ProvableCountProvableSumTree`. For the count axis the secondary
/// entry is a plain `Item` (count = 1, no sum).
#[allow(clippy::too_many_arguments)]
pub(crate) fn mirror_indexed_axis_to_secondary<'db, S: StorageContext<'db>>(
secondary: &mut Merk<S>,
Expand Down Expand Up @@ -2284,7 +2290,8 @@ pub(crate) fn mirror_indexed_axis_to_secondary<'db, S: StorageContext<'db>>(
// equality check above uses, so the two stay in lockstep:
// - Count → empty Item (secondary is a ProvableCountTree; every
// entry contributes count = 1)
// - Sum → SumItem(sum) (secondary is a ProvableSumTree)
// - Sum → SumItem(sum) (secondary is a
// ProvableCountProvableSumTree; contributes (1, sum))
// - Avg → ItemWithSumItem(empty, sum) (secondary is a
// ProvableCountProvableSumTree; contributes (1, sum))
let entry = axis_payload(new_sum_val);
Expand Down
8 changes: 4 additions & 4 deletions grovedb/src/operations/proof/indexed_axis/axis_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,10 +213,10 @@ impl GroveDb {
)
}

/// Prove an offset-paginated top-`k` window on the sum axis.
/// Note: the secondary is a `ProvableSumTree`, which has no
/// count-bound offset primitive, so the proof size is
/// O(offset + k). Use sparingly with large offsets.
/// Prove an offset-paginated top-`k` window on the sum axis. The
/// secondary is a `ProvableCountProvableSumTree`, so the skipped
/// prefix is attested by counted subtree commitments and the proof
/// size is O(log n + k) regardless of `offset`.
#[cfg(feature = "minimal")]
pub fn prove_indexed_sum_top_k_paginated<'b, B, P>(
&self,
Expand Down
48 changes: 23 additions & 25 deletions grovedb/src/operations/proof/indexed_axis/envelope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,15 +89,13 @@ pub struct IndexedAxisRangeProof {
/// Wire-format envelope for an offset-paginated top-k proof over an
/// indexed-tree's per-axis secondary.
///
/// For count and avg axes (`ProvableCountTree` / dual-axis
/// `ProvableCountProvableSumTree` secondaries) the secondary proof is
/// produced by `Merk::prove_count_offset_on_range`, giving
/// `O(log n + k)` proof size regardless of `offset`. For the sum axis
/// (`ProvableSumTree` secondary) there is no count-bound offset
/// primitive, so the prover instead emits a regular range proof with
/// `limit = offset + k` and the verifier discards the first `offset`
/// items independently. The `axis_tag` field disambiguates the two
/// shapes.
/// Every axis's secondary binds a count aggregate into its node hashes
/// (count axis: `ProvableCountTree`; sum and avg axes: dual-axis
/// `ProvableCountProvableSumTree`), so the secondary proof is always
/// produced by `Merk::prove_count_offset_on_range`: the skipped prefix
/// is attested by counted subtree commitments (`HashWithCount` /
/// `HashWithCountAndSum`), giving `O(log n + k)` proof size regardless
/// of `offset`.
#[derive(Encode, Decode, Debug)]
pub struct IndexedAxisPaginatedProof {
/// Echoed [`IndexAxis::tag`] of the queried axis. The verifier
Expand All @@ -113,12 +111,9 @@ pub struct IndexedAxisPaginatedProof {
pub other_axes_root_hashes: Vec<(u8, [u8; 32])>,
/// Same as [`IndexedAxisRangeProof::target_is_pcpsit`].
pub target_is_pcpsit: bool,
/// Encoded paginated proof bytes for the per-axis secondary.
///
/// For count/avg axes this is the
/// `prove_count_offset_on_range`-produced `Vec<Op>` stream. For
/// the sum axis this is a regular `Merk::prove`-produced range
/// proof bound by `limit = offset + k`.
/// Encoded paginated proof bytes for the per-axis secondary: the
/// `prove_count_offset_on_range`-produced `Vec<Op>` stream (every
/// axis's secondary carries a provable count).
pub secondary_proof: Vec<u8>,
/// Echoed pagination parameters.
pub requested_k: u16,
Expand Down Expand Up @@ -204,16 +199,19 @@ pub struct IndexedAxisPaginatedResult {
pub root_hash: CryptoHash,
/// Per-axis decoded entries (after the `skipped` offset region).
pub entries: AxisEntries,
/// Number of secondary entries the proof committed as skipped.
/// For count/avg axes this is independently re-derived by the
/// verifier from `HashWithCount` commitments in the proof bytes
/// (i.e. *cryptographically* committed). For the sum axis this
/// is the verifier-side count of items returned by the regular
/// range proof up to the `offset` cutoff — also independently
/// derived from the proof bytes, but constrained only by the
/// merk's regular range-walk discipline (NOT a count
/// commitment). The caller must cross-check
/// `skipped == expected_offset` if exact-page semantics matter.
/// Number of secondary entries the proof committed as skipped,
/// independently re-derived by the verifier from the counted
/// subtree commitments (`HashWithCount` / `HashWithCountAndSum`)
/// in the proof bytes — i.e. *cryptographically* attested for
/// every axis.
///
/// `skipped == requested_offset` unless the walk was exhausted
/// first, in which case `skipped < requested_offset` and
/// `entries` is empty — that shape is itself a proof that the
/// secondary's total population is exactly `skipped` (the counted
/// commitments cover the whole walk). Callers wanting strict
/// "page exists" semantics should cross-check
/// `skipped == expected_offset`.
pub skipped: u64,
}

Expand Down
Loading
Loading