data_anchor_proofs/
blob.rs1use std::{cmp::min, fmt::Debug};
4
5use data_anchor_blober::{CHUNK_SIZE, compute_blob_digest};
6use serde::{Deserialize, Serialize};
7use solana_sdk::hash::{HASH_BYTES, Hash};
8use thiserror::Error;
9
10#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
15pub struct BlobProof {
16 pub digest: [u8; 32],
18 pub chunk_order: Vec<u16>,
19}
20
21impl Debug for BlobProof {
22 #[cfg_attr(test, mutants::skip)]
23 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24 f.debug_struct("Proof")
25 .field("digest", &Hash::new_from_array(self.digest))
26 .field("chunk_order", &self.chunk_order)
27 .finish()
28 }
29}
30
31#[derive(Debug, Clone, Error, PartialEq, Eq)]
33pub enum BlobProofError {
34 #[error("Invalid structure when checking blob against stored chunks.")]
35 InvalidStructure,
36 #[error("Digest does not match the expected value. Expected {expected:?}, found {found:?}")]
37 DigestMismatch {
38 expected: [u8; HASH_BYTES],
39 found: [u8; HASH_BYTES],
40 },
41}
42
43impl BlobProof {
44 pub fn new<A: AsRef<[u8]>>(chunks: &[(u16, A)]) -> Self {
46 let digest = compute_blob_digest(chunks);
47 let chunk_order = chunks.iter().map(|(i, _)| *i).collect();
48 Self {
49 digest,
50 chunk_order,
51 }
52 }
53
54 pub fn verify(&self, blob: &[u8]) -> Result<(), BlobProofError> {
56 let chunk_size = CHUNK_SIZE as usize;
57 let chunks = self
58 .chunk_order
59 .iter()
60 .map(|&i| {
61 let start_offset = i as usize * chunk_size;
62 let end_offset = min(start_offset + chunk_size, blob.len());
63
64 match blob.get(start_offset..end_offset) {
65 Some(chunk) => Ok((i, chunk)),
66 None => Err(BlobProofError::InvalidStructure),
67 }
68 })
69 .collect::<Result<Vec<_>, BlobProofError>>()?;
70
71 let digest = compute_blob_digest(&chunks);
72
73 if self.digest == digest {
74 Ok(())
75 } else {
76 Err(BlobProofError::DigestMismatch {
77 expected: self.digest,
78 found: digest,
79 })
80 }
81 }
82}
83
84#[cfg(test)]
85mod tests {
86 use arbtest::arbtest;
87 use data_anchor_blober::CHUNK_SIZE;
88
89 use super::*;
90
91 #[test]
92 fn empty_blob() {
93 BlobProof::new::<&[u8]>(&[]).verify(&[]).unwrap();
94 }
95
96 #[test]
97 fn proof() {
98 arbtest(|u| {
99 let data = u.arbitrary::<Vec<u8>>()?;
100 if data.is_empty() {
101 return Ok(());
103 }
104 let mut chunks = data
105 .chunks(CHUNK_SIZE as usize)
106 .enumerate()
107 .map(|(i, c)| (i as u16, c))
108 .collect::<Vec<_>>();
109 for _ in 0..u.arbitrary_len::<usize>()? {
110 let a = u.choose_index(chunks.len())?;
111 let b = u.choose_index(chunks.len())?;
112 chunks.swap(a, b);
113 }
114 let proof = BlobProof::new(&chunks);
115 proof.verify(&data).unwrap();
116 Ok(())
117 })
118 .size_max(100_000_000);
119 }
120
121 #[test]
122 fn false_proof() {
123 arbtest(|u| {
124 let mut data = u.arbitrary::<Vec<u8>>()?;
125 if data.is_empty() {
126 return Ok(());
128 }
129 let chunks = data
130 .chunks(CHUNK_SIZE as usize)
131 .enumerate()
132 .map(|(i, c)| (i as u16, c))
133 .collect::<Vec<_>>();
134
135 let proof = BlobProof::new(&chunks);
136 let other = 1 + u.choose_index(data.len() - 1)?;
138 let before = data.clone();
139 data.swap(0, other);
140 if data == before {
141 return Ok(());
143 }
144 proof.verify(&data).unwrap_err();
145 Ok(())
146 })
147 .size_max(100_000_000);
148 }
149}