Enforce rear padding data check for segment proofs. (#55)

* Construct wrong proof.

* Enforce rear padding data check for segment proofs.
This commit is contained in:
peilun-conflux 2024-04-17 18:46:04 +08:00 committed by GitHub
parent 6ca05090c3
commit b9c6e43617
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 65 additions and 6 deletions

View File

@ -9,7 +9,9 @@ use std::fmt::Debug;
use std::marker::PhantomData; use std::marker::PhantomData;
use tracing::warn; use tracing::warn;
pub use crate::merkle_tree::{Algorithm, HashElement, MerkleTreeInitialData, MerkleTreeRead}; pub use crate::merkle_tree::{
Algorithm, HashElement, MerkleTreeInitialData, MerkleTreeRead, ZERO_HASHES,
};
pub use proof::{Proof, RangeProof}; pub use proof::{Proof, RangeProof};
pub use sha3::Sha3Algorithm; pub use sha3::Sha3Algorithm;

View File

@ -1,13 +1,15 @@
use crate::error; use crate::error;
use append_merkle::ZERO_HASHES;
use jsonrpsee::core::RpcResult; use jsonrpsee::core::RpcResult;
use merkle_light::hash::Algorithm; use merkle_light::hash::Algorithm;
use merkle_light::merkle::MerkleTree; use merkle_light::merkle::{log2_pow2, next_pow2, MerkleTree};
use merkle_tree::RawLeafSha3Algorithm; use merkle_tree::RawLeafSha3Algorithm;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use shared_types::{ use shared_types::{
compute_padded_chunk_size, compute_segment_size, DataRoot, FileProof, Transaction, CHUNK_SIZE, compute_padded_chunk_size, compute_segment_size, DataRoot, FileProof, Transaction, CHUNK_SIZE,
}; };
use std::hash::Hasher; use std::hash::Hasher;
use storage::log_store::log_manager::bytes_to_entries;
use storage::H256; use storage::H256;
const ZERO_HASH: [u8; 32] = [ const ZERO_HASH: [u8; 32] = [
@ -132,7 +134,12 @@ impl SegmentWithProof {
MerkleTree::<_, RawLeafSha3Algorithm>::new(hash_data).root() MerkleTree::<_, RawLeafSha3Algorithm>::new(hash_data).root()
} }
fn validate_proof(&self, num_segments: usize, expected_data_length: usize) -> RpcResult<()> { fn validate_proof(
&self,
num_segments: usize,
chunks_per_segment: usize,
expected_data_length: usize,
) -> RpcResult<()> {
// Validate proof data format at first. // Validate proof data format at first.
if self.proof.path.is_empty() { if self.proof.path.is_empty() {
if self.proof.lemma.len() != 1 { if self.proof.lemma.len() != 1 {
@ -162,6 +169,19 @@ impl SegmentWithProof {
return Err(error::invalid_params("proof", "validation failed")); return Err(error::invalid_params("proof", "validation failed"));
} }
let chunks_for_file = bytes_to_entries(self.file_size as u64) as usize;
let (segments_for_file, _) = compute_segment_size(chunks_for_file, chunks_per_segment);
if !self.validate_rear_padding(
num_segments,
segments_for_file,
log2_pow2(chunks_per_segment),
) {
return Err(error::invalid_params(
"proof",
"invalid proof node value in the padding range",
));
}
Ok(()) Ok(())
} }
@ -185,10 +205,47 @@ impl SegmentWithProof {
expected_data_length expected_data_length
); );
self.validate_proof(segments_for_proof, expected_data_length)?; self.validate_proof(segments_for_proof, chunks_per_segment, expected_data_length)?;
Ok(()) Ok(())
} }
fn validate_rear_padding(
&self,
num_segments: usize,
num_segments_for_file: usize,
segment_tree_depth: usize,
) -> bool {
let mut left_chunk_count = num_segments;
let mut proof_position = 0;
for (i, is_left) in self.proof.path.iter().rev().enumerate() {
let subtree_size = next_pow2(left_chunk_count) >> 1;
if !is_left {
proof_position += subtree_size;
left_chunk_count -= subtree_size;
} else {
// The proof node is in the right, so it's possible to be in the rear padding range.
left_chunk_count = subtree_size;
// If all the data within the proof node range is padded, its value should match
// `ZERO_HASHES`.
if proof_position + subtree_size >= num_segments_for_file {
let subtree_depth = log2_pow2(subtree_size) + segment_tree_depth;
if self.proof.lemma[self.proof.lemma.len() - 2 - i]
!= ZERO_HASHES[subtree_depth]
{
warn!(
"invalid proof in the padding range: index={} get={:?} expected={:?}",
i,
self.proof.lemma[self.proof.lemma.len() - 2 - i],
ZERO_HASHES[subtree_depth]
);
return false;
}
}
}
}
true
}
/// Returns the index of first chunk in the segment. /// Returns the index of first chunk in the segment.
#[allow(dead_code)] #[allow(dead_code)]
pub fn chunk_index(&self, chunks_per_segment: usize) -> usize { pub fn chunk_index(&self, chunks_per_segment: usize) -> usize {

View File

@ -9,14 +9,14 @@ class ExampleTest(TestFramework):
def run_test(self): def run_test(self):
client = self.nodes[0] client = self.nodes[0]
chunk_data = b"\x00" * 256 chunk_data = b"\x02" * 5253123
submissions, data_root = create_submission(chunk_data) submissions, data_root = create_submission(chunk_data)
self.contract.submit(submissions) self.contract.submit(submissions)
wait_until(lambda: self.contract.num_submissions() == 1) wait_until(lambda: self.contract.num_submissions() == 1)
wait_until(lambda: client.zgs_get_file_info(data_root) is not None) wait_until(lambda: client.zgs_get_file_info(data_root) is not None)
segment = submit_data(client, chunk_data) segment = submit_data(client, chunk_data)
self.log.info("segment: %s", segment) self.log.info("segment: %s", len(segment))
wait_until(lambda: client.zgs_get_file_info(data_root)["finalized"]) wait_until(lambda: client.zgs_get_file_info(data_root)["finalized"])