decds_lib/chunk.rs
1use crate::{chunkset::ChunkSet, consts::DECDS_BINCODE_CONFIG, errors::DecdsError, merkle_tree::MerkleTree};
2use serde::{Deserialize, Serialize};
3
4/// Represents a fixed-size (1MB = 2^20 bytes) data chunk within a chunkset in erasure-coded form.
5/// It contains metadata about its origin and the RLNC erasure-coded data.
6#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
7pub(crate) struct Chunk {
8 chunkset_id: usize,
9 chunk_id: usize,
10 erasure_coded_data: Vec<u8>,
11}
12
13impl Chunk {
14 pub const BYTE_LENGTH: usize = 1usize << 20;
15
16 /// Creates a new `Chunk` instance.
17 ///
18 /// # Arguments
19 ///
20 /// * `chunkset_id` - The ID of the chunkset this chunk belongs to.
21 /// * `chunk_id` - The global ID of this chunk.
22 /// * `erasure_coded_data` - The RLNC erasure-coded data payload of the chunk.
23 ///
24 /// # Returns
25 ///
26 /// Returns a new `Chunk` instance.
27 pub fn new(chunkset_id: usize, chunk_id: usize, erasure_coded_data: Vec<u8>) -> Self {
28 Chunk {
29 chunkset_id,
30 chunk_id,
31 erasure_coded_data,
32 }
33 }
34
35 /// Computes the BLAKE3 digest of the byte serialized representation of this chunk.
36 ///
37 /// # Returns
38 ///
39 /// A `blake3::Hash` representing the digest of the chunk.
40 pub fn digest(&self) -> blake3::Hash {
41 blake3::Hasher::new()
42 .update(&self.chunkset_id.to_le_bytes())
43 .update(&self.chunk_id.to_le_bytes())
44 .update(&self.erasure_coded_data)
45 .finalize()
46 }
47}
48
49/// Represents a `Chunk` augmented with a Merkle proof of its inclusion in the original blob.
50/// This structure is used for verifiable data retrieval and reconstruction.
51#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
52pub struct ProofCarryingChunk {
53 chunk: Chunk,
54 proof: Vec<blake3::Hash>,
55}
56
57impl ProofCarryingChunk {
58 /// Creates a new `ProofCarryingChunk` instance.
59 ///
60 /// # Arguments
61 ///
62 /// * `chunk` - The underlying `Chunk` data.
63 /// * `proof` - A `Vec<blake3::Hash>` representing the Merkle inclusion proof for this chunk within its chunkset.
64 ///
65 /// # Assumes
66 ///
67 /// That `proof.len()` equals to `ChunkSet::PROOF_SIZE`.
68 pub(crate) fn new(chunk: Chunk, proof: Vec<blake3::Hash>) -> Self {
69 Self { chunk, proof }
70 }
71
72 /// Validates the inclusion of this chunk in the overall blob using the provided blob root commitment.
73 ///
74 /// This method verifies the Merkle proof against the blob's root commitment,
75 /// assuming the proof contains the necessary sibling nodes to ascend to the blob root.
76 ///
77 /// Meaning `Self::append_proof_to_blob_root()` needs to be called after `Self::new()` to extend
78 /// the Merkle inclusion proof to the blob root level - only then one can validate inclusion of this
79 /// chunk in the blob.
80 ///
81 /// # Arguments
82 ///
83 /// * `blob_commitment` - The `blake3::Hash` of the root of the Merkle tree for the entire blob.
84 ///
85 /// # Returns
86 ///
87 /// Returns `true` if the chunk's inclusion proof in the blob is valid, `false` otherwise.
88 pub fn validate_inclusion_in_blob(&self, blob_commitment: blake3::Hash) -> bool {
89 MerkleTree::verify_proof(self.get_global_chunk_id(), self.chunk.digest(), &self.proof, blob_commitment)
90 }
91
92 /// Validates the inclusion of this chunk within its specific chunkset using the provided chunkset root commitment.
93 ///
94 /// This method checks the Merkle proof against the chunkset's root commitment.
95 ///
96 /// # Arguments
97 ///
98 /// * `chunkset_commitment` - The `blake3::Hash` of the root of the Merkle tree for the chunkset this chunk belongs to.
99 ///
100 /// # Returns
101 ///
102 /// Returns `true` if the chunk's inclusion proof in its chunkset is valid, `false` otherwise.
103 pub fn validate_inclusion_in_chunkset(&self, chunkset_commitment: blake3::Hash) -> bool {
104 MerkleTree::verify_proof(
105 self.get_local_chunk_id(),
106 self.chunk.digest(),
107 &self.proof[..ChunkSet::PROOF_SIZE],
108 chunkset_commitment,
109 )
110 }
111
112 /// Returns the ID of the chunkset this chunk belongs to.
113 pub fn get_chunkset_id(&self) -> usize {
114 self.chunk.chunkset_id
115 }
116
117 /// Returns the global ID of the chunk.
118 pub fn get_global_chunk_id(&self) -> usize {
119 self.chunk.chunk_id
120 }
121
122 /// Returns the local ID of the chunk.
123 pub fn get_local_chunk_id(&self) -> usize {
124 self.chunk.chunk_id % ChunkSet::NUM_ERASURE_CODED_CHUNKS
125 }
126
127 /// Returns a reference to the erasure-coded data contained within the chunk.
128 pub fn get_erasure_coded_data(&self) -> &[u8] {
129 self.chunk.erasure_coded_data.as_ref()
130 }
131
132 /// Appends additional Merkle proof hashes to the existing proof, proving blob-level inclusion.
133 ///
134 /// This is used to extend a chunkset-level proof to a blob-level proof. You are supposed to call this
135 /// function after `Self::new` is used to contruct a new proof-carrying chunk, which originally holds a
136 /// proof of inclusion in the corresponding chunkset.
137 ///
138 /// # Arguments
139 ///
140 /// * `blob_proof` - A slice of `blake3::Hash` representing the proof to append.
141 pub fn append_proof_to_blob_root(&mut self, blob_proof: &[blake3::Hash]) {
142 self.proof.extend_from_slice(blob_proof);
143 }
144
145 /// Serializes the `ProofCarryingChunk` into a vector of bytes using `bincode`.
146 ///
147 /// # Returns
148 ///
149 /// Returns a `Result` which is:
150 /// - `Ok(Vec<u8>)` containing the serialized bytes if successful.
151 /// - `Err(DecdsError::ProofCarryingChunkSerializationFailed)` if serialization fails.
152 pub fn to_bytes(&self) -> Result<Vec<u8>, DecdsError> {
153 bincode::serde::encode_to_vec(self, DECDS_BINCODE_CONFIG).map_err(|err| DecdsError::ProofCarryingChunkSerializationFailed(err.to_string()))
154 }
155
156 /// Deserializes a `ProofCarryingChunk` from a byte slice using `bincode`.
157 ///
158 /// # Arguments
159 ///
160 /// * `bytes` - The byte slice from which to deserialize the chunk.
161 ///
162 /// # Returns
163 ///
164 /// Returns a `Result` which is:
165 /// - `Ok((Self, usize))` containing the deserialized `ProofCarryingChunk` and the number of bytes read if successful.
166 /// - `Err(DecdsError::ProofCarryingChunkDeserializationFailed)` if deserialization fails.
167 pub fn from_bytes(bytes: &[u8]) -> Result<(Self, usize), DecdsError> {
168 bincode::serde::decode_from_slice::<ProofCarryingChunk, bincode::config::Configuration>(bytes, DECDS_BINCODE_CONFIG)
169 .map_err(|err| DecdsError::ProofCarryingChunkDeserializationFailed(err.to_string()))
170 }
171}
172
173#[cfg(test)]
174mod tests {
175 use super::*;
176 use blake3;
177 use rand::Rng;
178
179 #[test]
180 fn test_chunk_digest() {
181 let chunkset_id = 1;
182 let chunk_id = 5;
183 let erasure_coded_data = vec![1, 2, 3, 4, 5];
184
185 let chunk = Chunk::new(chunkset_id, chunk_id, erasure_coded_data.clone());
186 let computed_digest = chunk.digest();
187
188 // Manually compute the expected digest
189 let expected_digest = blake3::Hasher::new()
190 .update(&chunkset_id.to_le_bytes())
191 .update(&chunk_id.to_le_bytes())
192 .update(&erasure_coded_data)
193 .finalize();
194
195 assert_eq!(computed_digest, expected_digest);
196
197 // Test with different data to ensure digest changes
198 let chunk2 = Chunk::new(chunkset_id, chunk_id, vec![6, 7, 8]);
199 assert_ne!(chunk2.digest(), expected_digest);
200 }
201
202 #[test]
203 fn test_proof_carrying_chunk_serialization_deserialization() {
204 let mut rng = rand::rng();
205
206 let chunkset_id = 0;
207 let chunk_id = 5;
208 let erasure_coded_data: Vec<u8> = (0..Chunk::BYTE_LENGTH).map(|_| rng.random()).collect();
209
210 // Generate a proof with a length consistent with ChunkSet::PROOF_SIZE
211 let proof_data = (0..ChunkSet::PROOF_SIZE)
212 .map(|_| {
213 let random_bytes: [u8; 32] = rng.random();
214 blake3::Hash::from_bytes(random_bytes)
215 })
216 .collect::<Vec<blake3::Hash>>();
217
218 let original_chunk = Chunk::new(chunkset_id, chunk_id, erasure_coded_data);
219 let original_pcc = ProofCarryingChunk::new(original_chunk.clone(), proof_data.clone());
220
221 // Test serialization
222 let serialized_pcc_bytes = original_pcc.to_bytes().expect("Serialization failed");
223
224 // Test deserialization
225 let (deserialized_pcc, bytes_read) = ProofCarryingChunk::from_bytes(&serialized_pcc_bytes).expect("Deserialization failed");
226
227 assert_eq!(original_pcc, deserialized_pcc);
228 assert_eq!(serialized_pcc_bytes.len(), bytes_read);
229
230 // Test deserialization with lesser bytes
231 assert!(ProofCarryingChunk::from_bytes(&serialized_pcc_bytes[..(serialized_pcc_bytes.len() / 2)]).is_err());
232 }
233}