casper_hashing/
chunk_with_proof.rs

1use datasize::DataSize;
2use schemars::JsonSchema;
3use serde::{Deserialize, Serialize};
4
5use casper_types::bytesrepr::{self, Bytes, FromBytes, ToBytes};
6
7use crate::{
8    error::{ChunkWithProofVerificationError, MerkleConstructionError},
9    indexed_merkle_proof::IndexedMerkleProof,
10    Digest,
11};
12
13/// Represents a chunk of data with attached proof.
14#[derive(DataSize, PartialEq, Eq, Debug, Clone, JsonSchema, Serialize, Deserialize)]
15#[serde(deny_unknown_fields)]
16pub struct ChunkWithProof {
17    proof: IndexedMerkleProof,
18    #[schemars(with = "String", description = "Hex-encoded bytes.")]
19    chunk: Bytes,
20}
21
22impl ToBytes for ChunkWithProof {
23    fn write_bytes(&self, buf: &mut Vec<u8>) -> Result<(), bytesrepr::Error> {
24        buf.append(&mut self.proof.to_bytes()?);
25        buf.append(&mut self.chunk.to_bytes()?);
26
27        Ok(())
28    }
29
30    fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
31        let mut result = bytesrepr::allocate_buffer(self)?;
32        self.write_bytes(&mut result)?;
33        Ok(result)
34    }
35
36    fn serialized_length(&self) -> usize {
37        self.proof.serialized_length() + self.chunk.serialized_length()
38    }
39}
40
41impl FromBytes for ChunkWithProof {
42    fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
43        let (proof, remainder) = FromBytes::from_bytes(bytes)?;
44        let (chunk, remainder) = FromBytes::from_bytes(remainder)?;
45
46        Ok((ChunkWithProof { proof, chunk }, remainder))
47    }
48}
49
50impl ChunkWithProof {
51    #[cfg(test)]
52    /// 10 bytes for testing purposes.
53    pub const CHUNK_SIZE_BYTES: usize = 10;
54
55    #[cfg(not(test))]
56    /// 8 MiB
57    pub const CHUNK_SIZE_BYTES: usize = 8 * 1024 * 1024;
58
59    /// Constructs the [`ChunkWithProof`] that contains the chunk of data with the appropriate index
60    /// and the cryptographic proof.
61    ///
62    /// Empty data is always represented as single, empty chunk and not as zero chunks.
63    pub fn new(data: &[u8], index: u64) -> Result<Self, MerkleConstructionError> {
64        Ok(if data.is_empty() {
65            ChunkWithProof {
66                proof: IndexedMerkleProof::new([Digest::blake2b_hash([])], index)?,
67                chunk: Bytes::new(),
68            }
69        } else {
70            ChunkWithProof {
71                proof: IndexedMerkleProof::new(
72                    data.chunks(Self::CHUNK_SIZE_BYTES)
73                        .map(Digest::blake2b_hash),
74                    index,
75                )?,
76                chunk: Bytes::from(
77                    data.chunks(Self::CHUNK_SIZE_BYTES)
78                        .nth(index as usize)
79                        .ok_or_else(|| MerkleConstructionError::IndexOutOfBounds {
80                            count: data.chunks(Self::CHUNK_SIZE_BYTES).len() as u64,
81                            index,
82                        })?,
83                ),
84            }
85        })
86    }
87
88    /// Get a reference to the `ChunkWithProof`'s chunk.
89    pub fn chunk(&self) -> &[u8] {
90        self.chunk.as_slice()
91    }
92
93    /// Convert a chunk with proof into the underlying chunk.
94    pub fn into_chunk(self) -> Bytes {
95        self.chunk
96    }
97
98    /// Returns the `IndexedMerkleProof`.
99    pub fn proof(&self) -> &IndexedMerkleProof {
100        &self.proof
101    }
102
103    /// Verify the integrity of this chunk with indexed Merkle proof.
104    pub fn verify(&self) -> Result<(), ChunkWithProofVerificationError> {
105        self.proof().verify()?;
106        let first_digest_in_indexed_merkle_proof =
107            self.proof().merkle_proof().first().ok_or_else(|| {
108                ChunkWithProofVerificationError::ChunkWithProofHasEmptyMerkleProof {
109                    chunk_with_proof: self.clone(),
110                }
111            })?;
112        let hash_of_chunk = Digest::hash(self.chunk());
113        if *first_digest_in_indexed_merkle_proof != hash_of_chunk {
114            return Err(
115                ChunkWithProofVerificationError::FirstDigestInMerkleProofDidNotMatchHashOfChunk {
116                    first_digest_in_indexed_merkle_proof: *first_digest_in_indexed_merkle_proof,
117                    hash_of_chunk,
118                },
119            );
120        }
121        Ok(())
122    }
123}
124
125#[cfg(test)]
126mod tests {
127    use proptest::{
128        arbitrary::Arbitrary,
129        strategy::{BoxedStrategy, Strategy},
130    };
131    use proptest_attr_macro::proptest;
132    use rand::Rng;
133
134    use casper_types::bytesrepr::{self, FromBytes, ToBytes};
135
136    use crate::{chunk_with_proof::ChunkWithProof, error::MerkleConstructionError, Digest};
137
138    fn prepare_bytes(length: usize) -> Vec<u8> {
139        let mut rng = rand::thread_rng();
140
141        (0..length).map(|_| rng.gen()).collect()
142    }
143
144    fn random_chunk_with_proof() -> ChunkWithProof {
145        let mut rng = rand::thread_rng();
146        let data: Vec<u8> = prepare_bytes(rng.gen_range(1..1024));
147        let index = rng.gen_range(0..data.chunks(ChunkWithProof::CHUNK_SIZE_BYTES).len() as u64);
148
149        ChunkWithProof::new(&data, index).unwrap()
150    }
151
152    impl ChunkWithProof {
153        fn replace_first_proof(self) -> Self {
154            let mut rng = rand::thread_rng();
155            let ChunkWithProof { mut proof, chunk } = self;
156
157            // Keep the same number of proofs, but replace the first one with some random hash
158            let mut merkle_proof: Vec<_> = proof.merkle_proof().to_vec();
159            merkle_proof.pop();
160            merkle_proof.insert(0, Digest::hash(rng.gen::<usize>().to_string()));
161            proof.inject_merkle_proof(merkle_proof);
162
163            ChunkWithProof { proof, chunk }
164        }
165    }
166
167    #[derive(Debug)]
168    pub struct TestDataSize(usize);
169    impl Arbitrary for TestDataSize {
170        type Parameters = ();
171        type Strategy = BoxedStrategy<Self>;
172
173        fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
174            (0usize..32usize)
175                .prop_map(|chunk_count| {
176                    TestDataSize(chunk_count * ChunkWithProof::CHUNK_SIZE_BYTES)
177                })
178                .boxed()
179        }
180    }
181
182    #[derive(Debug)]
183    pub struct TestDataSizeAtLeastTwoChunks(usize);
184    impl Arbitrary for TestDataSizeAtLeastTwoChunks {
185        type Parameters = ();
186        type Strategy = BoxedStrategy<Self>;
187
188        fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
189            (2usize..32usize)
190                .prop_map(|chunk_count| {
191                    TestDataSizeAtLeastTwoChunks(chunk_count * ChunkWithProof::CHUNK_SIZE_BYTES)
192                })
193                .boxed()
194        }
195    }
196
197    #[proptest]
198    fn generates_valid_proof(test_data: TestDataSize) {
199        for data in [prepare_bytes(test_data.0), vec![0u8; test_data.0]] {
200            let number_of_chunks: u64 = data
201                .chunks(ChunkWithProof::CHUNK_SIZE_BYTES)
202                .len()
203                .try_into()
204                .unwrap();
205
206            assert!((0..number_of_chunks)
207                .map(|chunk_index| { ChunkWithProof::new(data.as_slice(), chunk_index).unwrap() })
208                .all(|chunk_with_proof| chunk_with_proof.verify().is_ok()));
209        }
210    }
211
212    #[proptest]
213    fn validate_chunks_against_hash_merkle_tree(test_data: TestDataSizeAtLeastTwoChunks) {
214        // This test requires at least two chunks
215        assert!(test_data.0 >= ChunkWithProof::CHUNK_SIZE_BYTES * 2);
216
217        for data in [prepare_bytes(test_data.0), vec![0u8; test_data.0]] {
218            let expected_root = Digest::hash_merkle_tree(
219                data.chunks(ChunkWithProof::CHUNK_SIZE_BYTES)
220                    .map(Digest::hash),
221            );
222
223            // Calculate proof with `ChunkWithProof`
224            let ChunkWithProof {
225                proof: proof_0,
226                chunk: _,
227            } = ChunkWithProof::new(data.as_slice(), 0).unwrap();
228            let ChunkWithProof {
229                proof: proof_1,
230                chunk: _,
231            } = ChunkWithProof::new(data.as_slice(), 1).unwrap();
232
233            assert_eq!(proof_0.root_hash(), expected_root);
234            assert_eq!(proof_1.root_hash(), expected_root);
235        }
236    }
237
238    #[proptest]
239    fn verifies_chunk_with_proofs(test_data: TestDataSize) {
240        for data in [prepare_bytes(test_data.0), vec![0u8; test_data.0]] {
241            let chunk_with_proof = ChunkWithProof::new(data.as_slice(), 0).unwrap();
242            assert!(chunk_with_proof.verify().is_ok());
243
244            let chunk_with_incorrect_proof = chunk_with_proof.replace_first_proof();
245            assert!(chunk_with_incorrect_proof.verify().is_err());
246        }
247    }
248
249    #[proptest]
250    fn serde_deserialization_of_malformed_chunk_should_work(test_data: TestDataSize) {
251        for data in [prepare_bytes(test_data.0), vec![0u8; test_data.0]] {
252            let chunk_with_proof = ChunkWithProof::new(data.as_slice(), 0).unwrap();
253
254            let json = serde_json::to_string(&chunk_with_proof).unwrap();
255            assert_eq!(
256                chunk_with_proof,
257                serde_json::from_str::<ChunkWithProof>(&json)
258                    .expect("should deserialize correctly")
259            );
260
261            let chunk_with_incorrect_proof = chunk_with_proof.replace_first_proof();
262            let json = serde_json::to_string(&chunk_with_incorrect_proof).unwrap();
263            serde_json::from_str::<ChunkWithProof>(&json).expect("should deserialize correctly");
264        }
265    }
266
267    #[proptest]
268    fn bytesrepr_deserialization_of_malformed_chunk_should_work(test_data: TestDataSize) {
269        for data in [prepare_bytes(test_data.0), vec![0u8; test_data.0]] {
270            let chunk_with_proof = ChunkWithProof::new(data.as_slice(), 0).unwrap();
271
272            let bytes = chunk_with_proof
273                .to_bytes()
274                .expect("should serialize correctly");
275
276            let (deserialized_chunk_with_proof, _) =
277                ChunkWithProof::from_bytes(&bytes).expect("should deserialize correctly");
278
279            assert_eq!(chunk_with_proof, deserialized_chunk_with_proof);
280
281            let chunk_with_incorrect_proof = chunk_with_proof.replace_first_proof();
282            let bytes = chunk_with_incorrect_proof
283                .to_bytes()
284                .expect("should serialize correctly");
285
286            ChunkWithProof::from_bytes(&bytes).expect("should deserialize correctly");
287        }
288    }
289
290    #[test]
291    fn returns_error_on_incorrect_index() {
292        // This test needs specific data sizes, hence it doesn't use the proptest
293
294        let chunk_with_proof = ChunkWithProof::new(&[], 0).expect("should create with empty data");
295        assert!(chunk_with_proof.verify().is_ok());
296
297        let chunk_with_proof =
298            ChunkWithProof::new(&[], 1).expect_err("should error with empty data and index > 0");
299        if let MerkleConstructionError::IndexOutOfBounds { count, index } = chunk_with_proof {
300            assert_eq!(count, 1);
301            assert_eq!(index, 1);
302        } else {
303            panic!("expected MerkleConstructionError::IndexOutOfBounds");
304        }
305
306        let data_larger_than_single_chunk = vec![0u8; ChunkWithProof::CHUNK_SIZE_BYTES * 10];
307        ChunkWithProof::new(data_larger_than_single_chunk.as_slice(), 9).unwrap();
308
309        let chunk_with_proof =
310            ChunkWithProof::new(data_larger_than_single_chunk.as_slice(), 10).unwrap_err();
311        if let MerkleConstructionError::IndexOutOfBounds { count, index } = chunk_with_proof {
312            assert_eq!(count, 10);
313            assert_eq!(index, 10);
314        } else {
315            panic!("expected MerkleConstructionError::IndexOutOfBounds");
316        }
317    }
318
319    #[test]
320    fn bytesrepr_serialization() {
321        let chunk_with_proof = random_chunk_with_proof();
322        bytesrepr::test_serialization_roundtrip(&chunk_with_proof);
323    }
324
325    #[test]
326    fn chunk_with_empty_data_contains_a_single_proof() {
327        let chunk_with_proof = ChunkWithProof::new(&[], 0).unwrap();
328        assert_eq!(chunk_with_proof.proof.merkle_proof().len(), 1)
329    }
330}