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