Skip to content
Open
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
1 change: 1 addition & 0 deletions grovedb-version/src/version/grovedb_versions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ pub struct GroveDBOperationsProofVersions {
pub verify_subset_query_with_absence_proof: FeatureVersion,
pub verify_query_with_chained_path_queries: FeatureVersion,
pub verify_query_get_parent_tree_info_with_options: FeatureVersion,
pub decode_rejects_trailing_bytes: FeatureVersion,
}

#[derive(Clone, Debug, Default)]
Expand Down
1 change: 1 addition & 0 deletions grovedb-version/src/version/v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ pub const GROVE_V1: GroveVersion = GroveVersion {
verify_subset_query_with_absence_proof: 0,
verify_query_with_chained_path_queries: 0,
verify_query_get_parent_tree_info_with_options: 0,
decode_rejects_trailing_bytes: 0,
},
average_case: GroveDBOperationsAverageCaseVersions {
add_average_case_get_merk_at_path: 0,
Expand Down
1 change: 1 addition & 0 deletions grovedb-version/src/version/v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ pub const GROVE_V2: GroveVersion = GroveVersion {
verify_subset_query_with_absence_proof: 0,
verify_query_with_chained_path_queries: 0,
verify_query_get_parent_tree_info_with_options: 0,
decode_rejects_trailing_bytes: 0,
},
average_case: GroveDBOperationsAverageCaseVersions {
add_average_case_get_merk_at_path: 0,
Expand Down
1 change: 1 addition & 0 deletions grovedb-version/src/version/v3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ pub const GROVE_V3: GroveVersion = GroveVersion {
verify_subset_query_with_absence_proof: 0,
verify_query_with_chained_path_queries: 0,
verify_query_get_parent_tree_info_with_options: 0,
decode_rejects_trailing_bytes: 1,
},
average_case: GroveDBOperationsAverageCaseVersions {
add_average_case_get_merk_at_path: 0,
Expand Down
4 changes: 2 additions & 2 deletions grovedb/src/operations/proof/aggregate_count/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ impl GroveDb {
// alongside the inner-Query shape rules.
let inner_range = path_query.validate_leaf_aggregate_count_on_range()?.clone();

let grovedb_proof = super::decode_grovedb_proof_canonical(proof)?;
let grovedb_proof = super::decode_grovedb_proof_versioned(proof, grove_version)?;
let path_keys: Vec<&[u8]> = path_query.path.iter().map(|p| p.as_slice()).collect();

let root_layer = require_v1_envelope(&grovedb_proof, path_query)?;
Expand Down Expand Up @@ -191,7 +191,7 @@ impl GroveDb {
// descent below is skipped (carrier_outer_items is None).
let classification = classification::classify_aggregate_count_path_query(path_query)?;

let grovedb_proof = super::decode_grovedb_proof_canonical(proof)?;
let grovedb_proof = super::decode_grovedb_proof_versioned(proof, grove_version)?;
let path_keys: Vec<&[u8]> = path_query.path.iter().map(|p| p.as_slice()).collect();

let root_layer = require_v1_envelope(&grovedb_proof, path_query)?;
Expand Down
4 changes: 2 additions & 2 deletions grovedb/src/operations/proof/aggregate_count_and_sum/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ impl GroveDb {
.validate_leaf_aggregate_count_and_sum_on_range()?
.clone();

let grovedb_proof = super::decode_grovedb_proof_canonical(proof)?;
let grovedb_proof = super::decode_grovedb_proof_versioned(proof, grove_version)?;
let path_keys: Vec<&[u8]> = path_query.path.iter().map(|p| p.as_slice()).collect();

let root_layer = require_v1_envelope(&grovedb_proof, path_query)?;
Expand Down Expand Up @@ -188,7 +188,7 @@ impl GroveDb {
let classification =
classification::classify_aggregate_count_and_sum_path_query(path_query)?;

let grovedb_proof = super::decode_grovedb_proof_canonical(proof)?;
let grovedb_proof = super::decode_grovedb_proof_versioned(proof, grove_version)?;
let path_keys: Vec<&[u8]> = path_query.path.iter().map(|p| p.as_slice()).collect();

let root_layer = require_v1_envelope(&grovedb_proof, path_query)?;
Expand Down
4 changes: 2 additions & 2 deletions grovedb/src/operations/proof/aggregate_sum/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ impl GroveDb {
// use `verify_aggregate_sum_query_per_key` instead.
let inner_range = path_query.validate_leaf_aggregate_sum_on_range()?.clone();

let grovedb_proof = super::decode_grovedb_proof_canonical(proof)?;
let grovedb_proof = super::decode_grovedb_proof_versioned(proof, grove_version)?;
let path_keys: Vec<&[u8]> = path_query.path.iter().map(|p| p.as_slice()).collect();

let root_layer = require_v1_envelope(&grovedb_proof, path_query)?;
Expand Down Expand Up @@ -187,7 +187,7 @@ impl GroveDb {
// descent below is skipped (carrier_outer_items is None).
let classification = classification::classify_aggregate_sum_path_query(path_query)?;

let grovedb_proof = super::decode_grovedb_proof_canonical(proof)?;
let grovedb_proof = super::decode_grovedb_proof_versioned(proof, grove_version)?;
let path_keys: Vec<&[u8]> = path_query.path.iter().map(|p| p.as_slice()).collect();

let root_layer = require_v1_envelope(&grovedb_proof, path_query)?;
Expand Down
27 changes: 22 additions & 5 deletions grovedb/src/operations/proof/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ use std::{collections::BTreeMap, fmt};
pub const MAX_PROOF_DEPTH: usize = 128;

/// Decode a serialized [`GroveDBProof`] envelope using the same bincode
/// configuration the prover writes out.
/// configuration the prover writes out and the trailing-byte policy selected
/// by the GroveDB version.
///
/// Decoding is canonical: trailing bytes beyond the encoded envelope are
/// rejected. Without this check the same `(RootHash, payload)` could be
Expand All @@ -35,15 +36,31 @@ pub const MAX_PROOF_DEPTH: usize = 128;
/// assumption a caller might rely on (caching, deduplication, hashing
/// the proof itself).
///
/// Shared by the aggregate-count and aggregate-sum verifier entry
/// points so the canonical-decode contract has exactly one definition.
pub(super) fn decode_grovedb_proof_canonical(proof: &[u8]) -> Result<GroveDBProof, Error> {
pub(super) fn decode_grovedb_proof_versioned(
proof: &[u8],
grove_version: &GroveVersion,
) -> Result<GroveDBProof, Error> {
decode_grovedb_proof_with_trailing_policy(
proof,
grove_version
.grovedb_versions
.operations
.proof
.decode_rejects_trailing_bytes
> 0,
)
}

fn decode_grovedb_proof_with_trailing_policy(
proof: &[u8],
reject_trailing_bytes: bool,
) -> Result<GroveDBProof, Error> {
let config = bincode::config::standard()
.with_big_endian()
.with_limit::<{ 256 * 1024 * 1024 }>();
let (decoded, consumed) = bincode::decode_from_slice(proof, config)
.map_err(|e| Error::CorruptedData(format!("unable to decode proof: {}", e)))?;
if consumed != proof.len() {
if reject_trailing_bytes && consumed != proof.len() {
return Err(Error::CorruptedData(format!(
"proof has {} trailing bytes after the encoded envelope",
proof.len() - consumed
Expand Down
8 changes: 4 additions & 4 deletions grovedb/src/operations/proof/verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ impl GroveDb {
// (verify_query_with_options, verify_query_raw,
// verify_query_get_parent_tree_info_with_options).

let grovedb_proof = super::decode_grovedb_proof_canonical(proof)?;
let grovedb_proof = super::decode_grovedb_proof_versioned(proof, grove_version)?;

let (root_hash, _, result) =
Self::verify_proof_internal(&grovedb_proof, query, options, grove_version)?;
Expand Down Expand Up @@ -104,7 +104,7 @@ impl GroveDb {
));
}

let grovedb_proof = super::decode_grovedb_proof_canonical(proof)?;
let grovedb_proof = super::decode_grovedb_proof_versioned(proof, grove_version)?;

let (root_hash, tree_feature_type, result) =
Self::verify_proof_internal(&grovedb_proof, query, options, grove_version)?;
Expand Down Expand Up @@ -132,7 +132,7 @@ impl GroveDb {
.proof
.verify_query_raw
);
let grovedb_proof = super::decode_grovedb_proof_canonical(proof)?;
let grovedb_proof = super::decode_grovedb_proof_versioned(proof, grove_version)?;

let (root_hash, _, result) = Self::verify_proof_raw_internal(
&grovedb_proof,
Expand Down Expand Up @@ -2221,7 +2221,7 @@ impl GroveDb {
query: &PathTrunkChunkQuery,
grove_version: &GroveVersion,
) -> Result<(CryptoHash, GroveTrunkQueryResult), Error> {
let grovedb_proof = super::decode_grovedb_proof_canonical(proof)?;
let grovedb_proof = super::decode_grovedb_proof_versioned(proof, grove_version)?;

match grovedb_proof {
GroveDBProof::V0(proof_v0) => {
Expand Down
38 changes: 37 additions & 1 deletion grovedb/src/tests/proof_advanced_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ mod tests {
///
/// `GroveDb::verify_query` (and its siblings `verify_query_raw`,
/// `verify_query_with_options`, `verify_trunk_chunk_proof`) all
/// route through `decode_grovedb_proof_canonical`, which rejects
/// route through `decode_grovedb_proof_versioned`, which rejects
/// any trailing bytes beyond the encoded envelope. Without this,
/// the same logical proof would have many distinct byte encodings
/// (a proof and the same proof with arbitrary suffix bytes), all
Expand Down Expand Up @@ -376,6 +376,42 @@ mod tests {
}
}

#[test]
fn verify_query_legacy_version_accepts_proof_with_trailing_bytes() {
let mut legacy_version = GroveVersion::latest().clone();
legacy_version
.grovedb_versions
.operations
.proof
.decode_rejects_trailing_bytes = 0;
let grove_version = &legacy_version;
let db = make_test_grovedb(grove_version);

db.insert(
[TEST_LEAF].as_ref(),
b"k",
Element::new_item(b"v".to_vec()),
None,
None,
grove_version,
)
.unwrap()
.expect("insert");

let mut query = Query::new();
query.insert_all();
let path_query = PathQuery::new_unsized(vec![TEST_LEAF.to_vec()], query);

let mut proof = db
.prove_query(&path_query, None, grove_version)
.unwrap()
.expect("prove_query");
proof.push(0u8);

GroveDb::verify_query(&proof, &path_query, grove_version)
.expect("legacy proof decoding should ignore trailing bytes");
}

#[test]
fn verify_query_with_options_limit() {
// Prove a query with a limit and verify the results are correctly
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1668,7 +1668,7 @@ mod tests {
}

/// Unparsable envelope bytes → bincode-decode rejection arm in
/// `verify_aggregate_count_and_sum_query` (`decode_grovedb_proof_canonical`).
/// `verify_aggregate_count_and_sum_query` (`decode_grovedb_proof_versioned`).
#[test]
fn combined_unparsable_envelope_is_rejected() {
use crate::tests::TEST_LEAF;
Expand Down
Loading